Third method of sweep selection
[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, 2010, 2011 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 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h"
130
131 #ifdef ENABLE_NLS
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 # define T_(s) gettext(s)
135 #else
136 # ifdef WIN32
137 #   define _(s) T_(s)
138 #   define N_(s) s
139 # else
140 #   define _(s) (s)
141 #   define N_(s) s
142 #   define T_(s) s
143 # endif
144 #endif
145
146
147 /* A point in time */
148 typedef struct {
149     long sec;  /* Assuming this is >= 32 bits */
150     int ms;    /* Assuming this is >= 16 bits */
151 } TimeMark;
152
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155                          char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157                       char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
169                                                                                 Board board));
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173                    /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185                            char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187                         int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
194
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
228
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
244
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 int endPV = -1;
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
252 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
256 Boolean partnerUp;
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
268 int chattingPartner;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
273
274 /* States for ics_getting_history */
275 #define H_FALSE 0
276 #define H_REQUESTED 1
277 #define H_GOT_REQ_HEADER 2
278 #define H_GOT_UNREQ_HEADER 3
279 #define H_GETTING_MOVES 4
280 #define H_GOT_UNWANTED_HEADER 5
281
282 /* whosays values for GameEnds */
283 #define GE_ICS 0
284 #define GE_ENGINE 1
285 #define GE_PLAYER 2
286 #define GE_FILE 3
287 #define GE_XBOARD 4
288 #define GE_ENGINE1 5
289 #define GE_ENGINE2 6
290
291 /* Maximum number of games in a cmail message */
292 #define CMAIL_MAX_GAMES 20
293
294 /* Different types of move when calling RegisterMove */
295 #define CMAIL_MOVE   0
296 #define CMAIL_RESIGN 1
297 #define CMAIL_DRAW   2
298 #define CMAIL_ACCEPT 3
299
300 /* Different types of result to remember for each game */
301 #define CMAIL_NOT_RESULT 0
302 #define CMAIL_OLD_RESULT 1
303 #define CMAIL_NEW_RESULT 2
304
305 /* Telnet protocol constants */
306 #define TN_WILL 0373
307 #define TN_WONT 0374
308 #define TN_DO   0375
309 #define TN_DONT 0376
310 #define TN_IAC  0377
311 #define TN_ECHO 0001
312 #define TN_SGA  0003
313 #define TN_PORT 23
314
315 char*
316 safeStrCpy( char *dst, const char *src, size_t count )
317 { // [HGM] made safe
318   int i;
319   assert( dst != NULL );
320   assert( src != NULL );
321   assert( count > 0 );
322
323   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
324   if(  i == count && dst[count-1] != NULLCHAR)
325     {
326       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
327       if(appData.debugMode)
328       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
329     }
330
331   return dst;
332 }
333
334 /* Some compiler can't cast u64 to double
335  * This function do the job for us:
336
337  * We use the highest bit for cast, this only
338  * works if the highest bit is not
339  * in use (This should not happen)
340  *
341  * We used this for all compiler
342  */
343 double
344 u64ToDouble(u64 value)
345 {
346   double r;
347   u64 tmp = value & u64Const(0x7fffffffffffffff);
348   r = (double)(s64)tmp;
349   if (value & u64Const(0x8000000000000000))
350        r +=  9.2233720368547758080e18; /* 2^63 */
351  return r;
352 }
353
354 /* Fake up flags for now, as we aren't keeping track of castling
355    availability yet. [HGM] Change of logic: the flag now only
356    indicates the type of castlings allowed by the rule of the game.
357    The actual rights themselves are maintained in the array
358    castlingRights, as part of the game history, and are not probed
359    by this function.
360  */
361 int
362 PosFlags(index)
363 {
364   int flags = F_ALL_CASTLE_OK;
365   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
366   switch (gameInfo.variant) {
367   case VariantSuicide:
368     flags &= ~F_ALL_CASTLE_OK;
369   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
370     flags |= F_IGNORE_CHECK;
371   case VariantLosers:
372     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
373     break;
374   case VariantAtomic:
375     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
376     break;
377   case VariantKriegspiel:
378     flags |= F_KRIEGSPIEL_CAPTURE;
379     break;
380   case VariantCapaRandom:
381   case VariantFischeRandom:
382     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
383   case VariantNoCastle:
384   case VariantShatranj:
385   case VariantCourier:
386   case VariantMakruk:
387     flags &= ~F_ALL_CASTLE_OK;
388     break;
389   default:
390     break;
391   }
392   return flags;
393 }
394
395 FILE *gameFileFP, *debugFP;
396
397 /*
398     [AS] Note: sometimes, the sscanf() function is used to parse the input
399     into a fixed-size buffer. Because of this, we must be prepared to
400     receive strings as long as the size of the input buffer, which is currently
401     set to 4K for Windows and 8K for the rest.
402     So, we must either allocate sufficiently large buffers here, or
403     reduce the size of the input buffer in the input reading part.
404 */
405
406 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
407 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
408 char thinkOutput1[MSG_SIZ*10];
409
410 ChessProgramState first, second;
411
412 /* premove variables */
413 int premoveToX = 0;
414 int premoveToY = 0;
415 int premoveFromX = 0;
416 int premoveFromY = 0;
417 int premovePromoChar = 0;
418 int gotPremove = 0;
419 Boolean alarmSounded;
420 /* end premove variables */
421
422 char *ics_prefix = "$";
423 int ics_type = ICS_GENERIC;
424
425 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
426 int pauseExamForwardMostMove = 0;
427 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
428 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
429 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
430 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
431 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
432 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
433 int whiteFlag = FALSE, blackFlag = FALSE;
434 int userOfferedDraw = FALSE;
435 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
436 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
437 int cmailMoveType[CMAIL_MAX_GAMES];
438 long ics_clock_paused = 0;
439 ProcRef icsPR = NoProc, cmailPR = NoProc;
440 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
441 GameMode gameMode = BeginningOfGame;
442 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
443 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
444 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
445 int hiddenThinkOutputState = 0; /* [AS] */
446 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
447 int adjudicateLossPlies = 6;
448 char white_holding[64], black_holding[64];
449 TimeMark lastNodeCountTime;
450 long lastNodeCount=0;
451 int shiftKey; // [HGM] set by mouse handler
452
453 int have_sent_ICS_logon = 0;
454 int movesPerSession;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 long timeControl_2; /* [AS] Allow separate time controls */
458 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
459 long timeRemaining[2][MAX_MOVES];
460 int matchGame = 0;
461 TimeMark programStartTime;
462 char ics_handle[MSG_SIZ];
463 int have_set_title = 0;
464
465 /* animateTraining preserves the state of appData.animate
466  * when Training mode is activated. This allows the
467  * response to be animated when appData.animate == TRUE and
468  * appData.animateDragging == TRUE.
469  */
470 Boolean animateTraining;
471
472 GameInfo gameInfo;
473
474 AppData appData;
475
476 Board boards[MAX_MOVES];
477 /* [HGM] Following 7 needed for accurate legality tests: */
478 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
479 signed char  initialRights[BOARD_FILES];
480 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
481 int   initialRulePlies, FENrulePlies;
482 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
483 int loadFlag = 0;
484 int shuffleOpenings;
485 int mute; // mute all sounds
486
487 // [HGM] vari: next 12 to save and restore variations
488 #define MAX_VARIATIONS 10
489 int framePtr = MAX_MOVES-1; // points to free stack entry
490 int storedGames = 0;
491 int savedFirst[MAX_VARIATIONS];
492 int savedLast[MAX_VARIATIONS];
493 int savedFramePtr[MAX_VARIATIONS];
494 char *savedDetails[MAX_VARIATIONS];
495 ChessMove savedResult[MAX_VARIATIONS];
496
497 void PushTail P((int firstMove, int lastMove));
498 Boolean PopTail P((Boolean annotate));
499 void CleanupTail P((void));
500
501 ChessSquare  FIDEArray[2][BOARD_FILES] = {
502     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
503         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
504     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
505         BlackKing, BlackBishop, BlackKnight, BlackRook }
506 };
507
508 ChessSquare twoKingsArray[2][BOARD_FILES] = {
509     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
510         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
511     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
512         BlackKing, BlackKing, BlackKnight, BlackRook }
513 };
514
515 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
516     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
517         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
518     { BlackRook, BlackMan, BlackBishop, BlackQueen,
519         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
520 };
521
522 ChessSquare SpartanArray[2][BOARD_FILES] = {
523     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
525     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
526         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
527 };
528
529 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
530     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
531         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
532     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
533         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
534 };
535
536 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
537     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
538         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
539     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
540         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
541 };
542
543 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
544     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
545         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackMan, BlackFerz,
547         BlackKing, BlackMan, BlackKnight, BlackRook }
548 };
549
550
551 #if (BOARD_FILES>=10)
552 ChessSquare ShogiArray[2][BOARD_FILES] = {
553     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
554         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
555     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
556         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
557 };
558
559 ChessSquare XiangqiArray[2][BOARD_FILES] = {
560     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
561         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
562     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
563         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
564 };
565
566 ChessSquare CapablancaArray[2][BOARD_FILES] = {
567     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
568         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
569     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
570         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
571 };
572
573 ChessSquare GreatArray[2][BOARD_FILES] = {
574     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
575         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
576     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
577         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
578 };
579
580 ChessSquare JanusArray[2][BOARD_FILES] = {
581     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
582         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
583     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
584         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
585 };
586
587 #ifdef GOTHIC
588 ChessSquare GothicArray[2][BOARD_FILES] = {
589     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
590         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
591     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
592         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
593 };
594 #else // !GOTHIC
595 #define GothicArray CapablancaArray
596 #endif // !GOTHIC
597
598 #ifdef FALCON
599 ChessSquare FalconArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
601         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
603         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
604 };
605 #else // !FALCON
606 #define FalconArray CapablancaArray
607 #endif // !FALCON
608
609 #else // !(BOARD_FILES>=10)
610 #define XiangqiPosition FIDEArray
611 #define CapablancaArray FIDEArray
612 #define GothicArray FIDEArray
613 #define GreatArray FIDEArray
614 #endif // !(BOARD_FILES>=10)
615
616 #if (BOARD_FILES>=12)
617 ChessSquare CourierArray[2][BOARD_FILES] = {
618     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
619         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
620     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
621         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
622 };
623 #else // !(BOARD_FILES>=12)
624 #define CourierArray CapablancaArray
625 #endif // !(BOARD_FILES>=12)
626
627
628 Board initialPosition;
629
630
631 /* Convert str to a rating. Checks for special cases of "----",
632
633    "++++", etc. Also strips ()'s */
634 int
635 string_to_rating(str)
636   char *str;
637 {
638   while(*str && !isdigit(*str)) ++str;
639   if (!*str)
640     return 0;   /* One of the special "no rating" cases */
641   else
642     return atoi(str);
643 }
644
645 void
646 ClearProgramStats()
647 {
648     /* Init programStats */
649     programStats.movelist[0] = 0;
650     programStats.depth = 0;
651     programStats.nr_moves = 0;
652     programStats.moves_left = 0;
653     programStats.nodes = 0;
654     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
655     programStats.score = 0;
656     programStats.got_only_move = 0;
657     programStats.got_fail = 0;
658     programStats.line_is_book = 0;
659 }
660
661 void
662 InitBackEnd1()
663 {
664     int matched, min, sec;
665
666     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
667     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
668
669     GetTimeMark(&programStartTime);
670     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
671
672     ClearProgramStats();
673     programStats.ok_to_send = 1;
674     programStats.seen_stat = 0;
675
676     /*
677      * Initialize game list
678      */
679     ListNew(&gameList);
680
681
682     /*
683      * Internet chess server status
684      */
685     if (appData.icsActive) {
686         appData.matchMode = FALSE;
687         appData.matchGames = 0;
688 #if ZIPPY
689         appData.noChessProgram = !appData.zippyPlay;
690 #else
691         appData.zippyPlay = FALSE;
692         appData.zippyTalk = FALSE;
693         appData.noChessProgram = TRUE;
694 #endif
695         if (*appData.icsHelper != NULLCHAR) {
696             appData.useTelnet = TRUE;
697             appData.telnetProgram = appData.icsHelper;
698         }
699     } else {
700         appData.zippyTalk = appData.zippyPlay = FALSE;
701     }
702
703     /* [AS] Initialize pv info list [HGM] and game state */
704     {
705         int i, j;
706
707         for( i=0; i<=framePtr; i++ ) {
708             pvInfoList[i].depth = -1;
709             boards[i][EP_STATUS] = EP_NONE;
710             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
711         }
712     }
713
714     /*
715      * Parse timeControl resource
716      */
717     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
718                           appData.movesPerSession)) {
719         char buf[MSG_SIZ];
720         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
721         DisplayFatalError(buf, 0, 2);
722     }
723
724     /*
725      * Parse searchTime resource
726      */
727     if (*appData.searchTime != NULLCHAR) {
728         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
729         if (matched == 1) {
730             searchTime = min * 60;
731         } else if (matched == 2) {
732             searchTime = min * 60 + sec;
733         } else {
734             char buf[MSG_SIZ];
735             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
736             DisplayFatalError(buf, 0, 2);
737         }
738     }
739
740     /* [AS] Adjudication threshold */
741     adjudicateLossThreshold = appData.adjudicateLossThreshold;
742
743     first.which = "first";
744     second.which = "second";
745     first.maybeThinking = second.maybeThinking = FALSE;
746     first.pr = second.pr = NoProc;
747     first.isr = second.isr = NULL;
748     first.sendTime = second.sendTime = 2;
749     first.sendDrawOffers = 1;
750     if (appData.firstPlaysBlack) {
751         first.twoMachinesColor = "black\n";
752         second.twoMachinesColor = "white\n";
753     } else {
754         first.twoMachinesColor = "white\n";
755         second.twoMachinesColor = "black\n";
756     }
757     first.program = appData.firstChessProgram;
758     second.program = appData.secondChessProgram;
759     first.host = appData.firstHost;
760     second.host = appData.secondHost;
761     first.dir = appData.firstDirectory;
762     second.dir = appData.secondDirectory;
763     first.other = &second;
764     second.other = &first;
765     first.initString = appData.initString;
766     second.initString = appData.secondInitString;
767     first.computerString = appData.firstComputerString;
768     second.computerString = appData.secondComputerString;
769     first.useSigint = second.useSigint = TRUE;
770     first.useSigterm = second.useSigterm = TRUE;
771     first.reuse = appData.reuseFirst;
772     second.reuse = appData.reuseSecond;
773     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
774     second.nps = appData.secondNPS;
775     first.useSetboard = second.useSetboard = FALSE;
776     first.useSAN = second.useSAN = FALSE;
777     first.usePing = second.usePing = FALSE;
778     first.lastPing = second.lastPing = 0;
779     first.lastPong = second.lastPong = 0;
780     first.usePlayother = second.usePlayother = FALSE;
781     first.useColors = second.useColors = TRUE;
782     first.useUsermove = second.useUsermove = FALSE;
783     first.sendICS = second.sendICS = FALSE;
784     first.sendName = second.sendName = appData.icsActive;
785     first.sdKludge = second.sdKludge = FALSE;
786     first.stKludge = second.stKludge = FALSE;
787     TidyProgramName(first.program, first.host, first.tidy);
788     TidyProgramName(second.program, second.host, second.tidy);
789     first.matchWins = second.matchWins = 0;
790     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
791     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
792     first.analysisSupport = second.analysisSupport = 2; /* detect */
793     first.analyzing = second.analyzing = FALSE;
794     first.initDone = second.initDone = FALSE;
795
796     /* New features added by Tord: */
797     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
798     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
799     /* End of new features added by Tord. */
800     first.fenOverride  = appData.fenOverride1;
801     second.fenOverride = appData.fenOverride2;
802
803     /* [HGM] time odds: set factor for each machine */
804     first.timeOdds  = appData.firstTimeOdds;
805     second.timeOdds = appData.secondTimeOdds;
806     { float norm = 1;
807         if(appData.timeOddsMode) {
808             norm = first.timeOdds;
809             if(norm > second.timeOdds) norm = second.timeOdds;
810         }
811         first.timeOdds /= norm;
812         second.timeOdds /= norm;
813     }
814
815     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
816     first.accumulateTC = appData.firstAccumulateTC;
817     second.accumulateTC = appData.secondAccumulateTC;
818     first.maxNrOfSessions = second.maxNrOfSessions = 1;
819
820     /* [HGM] debug */
821     first.debug = second.debug = FALSE;
822     first.supportsNPS = second.supportsNPS = UNKNOWN;
823
824     /* [HGM] options */
825     first.optionSettings  = appData.firstOptions;
826     second.optionSettings = appData.secondOptions;
827
828     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
829     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
830     first.isUCI = appData.firstIsUCI; /* [AS] */
831     second.isUCI = appData.secondIsUCI; /* [AS] */
832     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
833     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
834
835     if (appData.firstProtocolVersion > PROTOVER
836         || appData.firstProtocolVersion < 1)
837       {
838         char buf[MSG_SIZ];
839         int len;
840
841         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
842                        appData.firstProtocolVersion);
843         if( (len > MSG_SIZ) && appData.debugMode )
844           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
845
846         DisplayFatalError(buf, 0, 2);
847       }
848     else
849       {
850         first.protocolVersion = appData.firstProtocolVersion;
851       }
852
853     if (appData.secondProtocolVersion > PROTOVER
854         || appData.secondProtocolVersion < 1)
855       {
856         char buf[MSG_SIZ];
857         int len;
858
859         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
860                        appData.secondProtocolVersion);
861         if( (len > MSG_SIZ) && appData.debugMode )
862           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
863
864         DisplayFatalError(buf, 0, 2);
865       }
866     else
867       {
868         second.protocolVersion = appData.secondProtocolVersion;
869       }
870
871     if (appData.icsActive) {
872         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
873 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
874     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
875         appData.clockMode = FALSE;
876         first.sendTime = second.sendTime = 0;
877     }
878
879 #if ZIPPY
880     /* Override some settings from environment variables, for backward
881        compatibility.  Unfortunately it's not feasible to have the env
882        vars just set defaults, at least in xboard.  Ugh.
883     */
884     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
885       ZippyInit();
886     }
887 #endif
888
889     if (appData.noChessProgram) {
890         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
891         sprintf(programVersion, "%s", PACKAGE_STRING);
892     } else {
893       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
894       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
895       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
896     }
897
898     if (!appData.icsActive) {
899       char buf[MSG_SIZ];
900       int len;
901
902       /* Check for variants that are supported only in ICS mode,
903          or not at all.  Some that are accepted here nevertheless
904          have bugs; see comments below.
905       */
906       VariantClass variant = StringToVariant(appData.variant);
907       switch (variant) {
908       case VariantBughouse:     /* need four players and two boards */
909       case VariantKriegspiel:   /* need to hide pieces and move details */
910         /* case VariantFischeRandom: (Fabien: moved below) */
911         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
912         if( (len > MSG_SIZ) && appData.debugMode )
913           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
914
915         DisplayFatalError(buf, 0, 2);
916         return;
917
918       case VariantUnknown:
919       case VariantLoadable:
920       case Variant29:
921       case Variant30:
922       case Variant31:
923       case Variant32:
924       case Variant33:
925       case Variant34:
926       case Variant35:
927       case Variant36:
928       default:
929         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
930         if( (len > MSG_SIZ) && appData.debugMode )
931           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
932
933         DisplayFatalError(buf, 0, 2);
934         return;
935
936       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
937       case VariantFairy:      /* [HGM] TestLegality definitely off! */
938       case VariantGothic:     /* [HGM] should work */
939       case VariantCapablanca: /* [HGM] should work */
940       case VariantCourier:    /* [HGM] initial forced moves not implemented */
941       case VariantShogi:      /* [HGM] could still mate with pawn drop */
942       case VariantKnightmate: /* [HGM] should work */
943       case VariantCylinder:   /* [HGM] untested */
944       case VariantFalcon:     /* [HGM] untested */
945       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
946                                  offboard interposition not understood */
947       case VariantNormal:     /* definitely works! */
948       case VariantWildCastle: /* pieces not automatically shuffled */
949       case VariantNoCastle:   /* pieces not automatically shuffled */
950       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
951       case VariantLosers:     /* should work except for win condition,
952                                  and doesn't know captures are mandatory */
953       case VariantSuicide:    /* should work except for win condition,
954                                  and doesn't know captures are mandatory */
955       case VariantGiveaway:   /* should work except for win condition,
956                                  and doesn't know captures are mandatory */
957       case VariantTwoKings:   /* should work */
958       case VariantAtomic:     /* should work except for win condition */
959       case Variant3Check:     /* should work except for win condition */
960       case VariantShatranj:   /* should work except for all win conditions */
961       case VariantMakruk:     /* should work except for daw countdown */
962       case VariantBerolina:   /* might work if TestLegality is off */
963       case VariantCapaRandom: /* should work */
964       case VariantJanus:      /* should work */
965       case VariantSuper:      /* experimental */
966       case VariantGreat:      /* experimental, requires legality testing to be off */
967       case VariantSChess:     /* S-Chess, should work */
968       case VariantSpartan:    /* should work */
969         break;
970       }
971     }
972
973     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
974     InitEngineUCI( installDir, &second );
975 }
976
977 int NextIntegerFromString( char ** str, long * value )
978 {
979     int result = -1;
980     char * s = *str;
981
982     while( *s == ' ' || *s == '\t' ) {
983         s++;
984     }
985
986     *value = 0;
987
988     if( *s >= '0' && *s <= '9' ) {
989         while( *s >= '0' && *s <= '9' ) {
990             *value = *value * 10 + (*s - '0');
991             s++;
992         }
993
994         result = 0;
995     }
996
997     *str = s;
998
999     return result;
1000 }
1001
1002 int NextTimeControlFromString( char ** str, long * value )
1003 {
1004     long temp;
1005     int result = NextIntegerFromString( str, &temp );
1006
1007     if( result == 0 ) {
1008         *value = temp * 60; /* Minutes */
1009         if( **str == ':' ) {
1010             (*str)++;
1011             result = NextIntegerFromString( str, &temp );
1012             *value += temp; /* Seconds */
1013         }
1014     }
1015
1016     return result;
1017 }
1018
1019 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1020 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1021     int result = -1, type = 0; long temp, temp2;
1022
1023     if(**str != ':') return -1; // old params remain in force!
1024     (*str)++;
1025     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1026     if( NextIntegerFromString( str, &temp ) ) return -1;
1027     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1028
1029     if(**str != '/') {
1030         /* time only: incremental or sudden-death time control */
1031         if(**str == '+') { /* increment follows; read it */
1032             (*str)++;
1033             if(**str == '!') type = *(*str)++; // Bronstein TC
1034             if(result = NextIntegerFromString( str, &temp2)) return -1;
1035             *inc = temp2 * 1000;
1036             if(**str == '.') { // read fraction of increment
1037                 char *start = ++(*str);
1038                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1039                 temp2 *= 1000;
1040                 while(start++ < *str) temp2 /= 10;
1041                 *inc += temp2;
1042             }
1043         } else *inc = 0;
1044         *moves = 0; *tc = temp * 1000; *incType = type;
1045         return 0;
1046     }
1047
1048     (*str)++; /* classical time control */
1049     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1050
1051     if(result == 0) {
1052         *moves = temp;
1053         *tc    = temp2 * 1000;
1054         *inc   = 0;
1055         *incType = type;
1056     }
1057     return result;
1058 }
1059
1060 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1061 {   /* [HGM] get time to add from the multi-session time-control string */
1062     int incType, moves=1; /* kludge to force reading of first session */
1063     long time, increment;
1064     char *s = tcString;
1065
1066     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1067     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1068     do {
1069         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1070         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1071         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1072         if(movenr == -1) return time;    /* last move before new session     */
1073         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1074         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1075         if(!moves) return increment;     /* current session is incremental   */
1076         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1077     } while(movenr >= -1);               /* try again for next session       */
1078
1079     return 0; // no new time quota on this move
1080 }
1081
1082 int
1083 ParseTimeControl(tc, ti, mps)
1084      char *tc;
1085      float ti;
1086      int mps;
1087 {
1088   long tc1;
1089   long tc2;
1090   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1091   int min, sec=0;
1092
1093   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1094   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1095       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1096   if(ti > 0) {
1097
1098     if(mps)
1099       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1100     else 
1101       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1102   } else {
1103     if(mps)
1104       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1105     else 
1106       snprintf(buf, MSG_SIZ, ":%s", mytc);
1107   }
1108   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1109   
1110   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1111     return FALSE;
1112   }
1113
1114   if( *tc == '/' ) {
1115     /* Parse second time control */
1116     tc++;
1117
1118     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1119       return FALSE;
1120     }
1121
1122     if( tc2 == 0 ) {
1123       return FALSE;
1124     }
1125
1126     timeControl_2 = tc2 * 1000;
1127   }
1128   else {
1129     timeControl_2 = 0;
1130   }
1131
1132   if( tc1 == 0 ) {
1133     return FALSE;
1134   }
1135
1136   timeControl = tc1 * 1000;
1137
1138   if (ti >= 0) {
1139     timeIncrement = ti * 1000;  /* convert to ms */
1140     movesPerSession = 0;
1141   } else {
1142     timeIncrement = 0;
1143     movesPerSession = mps;
1144   }
1145   return TRUE;
1146 }
1147
1148 void
1149 InitBackEnd2()
1150 {
1151     if (appData.debugMode) {
1152         fprintf(debugFP, "%s\n", programVersion);
1153     }
1154
1155     set_cont_sequence(appData.wrapContSeq);
1156     if (appData.matchGames > 0) {
1157         appData.matchMode = TRUE;
1158     } else if (appData.matchMode) {
1159         appData.matchGames = 1;
1160     }
1161     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1162         appData.matchGames = appData.sameColorGames;
1163     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1164         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1165         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1166     }
1167     Reset(TRUE, FALSE);
1168     if (appData.noChessProgram || first.protocolVersion == 1) {
1169       InitBackEnd3();
1170     } else {
1171       /* kludge: allow timeout for initial "feature" commands */
1172       FreezeUI();
1173       DisplayMessage("", _("Starting chess program"));
1174       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1175     }
1176 }
1177
1178 void
1179 InitBackEnd3 P((void))
1180 {
1181     GameMode initialMode;
1182     char buf[MSG_SIZ];
1183     int err, len;
1184
1185     InitChessProgram(&first, startedFromSetupPosition);
1186
1187     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1188         free(programVersion);
1189         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1190         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1191     }
1192
1193     if (appData.icsActive) {
1194 #ifdef WIN32
1195         /* [DM] Make a console window if needed [HGM] merged ifs */
1196         ConsoleCreate();
1197 #endif
1198         err = establish();
1199         if (err != 0)
1200           {
1201             if (*appData.icsCommPort != NULLCHAR)
1202               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1203                              appData.icsCommPort);
1204             else
1205               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1206                         appData.icsHost, appData.icsPort);
1207
1208             if( (len > MSG_SIZ) && appData.debugMode )
1209               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1210
1211             DisplayFatalError(buf, err, 1);
1212             return;
1213         }
1214         SetICSMode();
1215         telnetISR =
1216           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1217         fromUserISR =
1218           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1219         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1220             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1221     } else if (appData.noChessProgram) {
1222         SetNCPMode();
1223     } else {
1224         SetGNUMode();
1225     }
1226
1227     if (*appData.cmailGameName != NULLCHAR) {
1228         SetCmailMode();
1229         OpenLoopback(&cmailPR);
1230         cmailISR =
1231           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1232     }
1233
1234     ThawUI();
1235     DisplayMessage("", "");
1236     if (StrCaseCmp(appData.initialMode, "") == 0) {
1237       initialMode = BeginningOfGame;
1238     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1239       initialMode = TwoMachinesPlay;
1240     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1241       initialMode = AnalyzeFile;
1242     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1243       initialMode = AnalyzeMode;
1244     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1245       initialMode = MachinePlaysWhite;
1246     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1247       initialMode = MachinePlaysBlack;
1248     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1249       initialMode = EditGame;
1250     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1251       initialMode = EditPosition;
1252     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1253       initialMode = Training;
1254     } else {
1255       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1256       if( (len > MSG_SIZ) && appData.debugMode )
1257         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1258
1259       DisplayFatalError(buf, 0, 2);
1260       return;
1261     }
1262
1263     if (appData.matchMode) {
1264         /* Set up machine vs. machine match */
1265         if (appData.noChessProgram) {
1266             DisplayFatalError(_("Can't have a match with no chess programs"),
1267                               0, 2);
1268             return;
1269         }
1270         matchMode = TRUE;
1271         matchGame = 1;
1272         if (*appData.loadGameFile != NULLCHAR) {
1273             int index = appData.loadGameIndex; // [HGM] autoinc
1274             if(index<0) lastIndex = index = 1;
1275             if (!LoadGameFromFile(appData.loadGameFile,
1276                                   index,
1277                                   appData.loadGameFile, FALSE)) {
1278                 DisplayFatalError(_("Bad game file"), 0, 1);
1279                 return;
1280             }
1281         } else if (*appData.loadPositionFile != NULLCHAR) {
1282             int index = appData.loadPositionIndex; // [HGM] autoinc
1283             if(index<0) lastIndex = index = 1;
1284             if (!LoadPositionFromFile(appData.loadPositionFile,
1285                                       index,
1286                                       appData.loadPositionFile)) {
1287                 DisplayFatalError(_("Bad position file"), 0, 1);
1288                 return;
1289             }
1290         }
1291         TwoMachinesEvent();
1292     } else if (*appData.cmailGameName != NULLCHAR) {
1293         /* Set up cmail mode */
1294         ReloadCmailMsgEvent(TRUE);
1295     } else {
1296         /* Set up other modes */
1297         if (initialMode == AnalyzeFile) {
1298           if (*appData.loadGameFile == NULLCHAR) {
1299             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1300             return;
1301           }
1302         }
1303         if (*appData.loadGameFile != NULLCHAR) {
1304             (void) LoadGameFromFile(appData.loadGameFile,
1305                                     appData.loadGameIndex,
1306                                     appData.loadGameFile, TRUE);
1307         } else if (*appData.loadPositionFile != NULLCHAR) {
1308             (void) LoadPositionFromFile(appData.loadPositionFile,
1309                                         appData.loadPositionIndex,
1310                                         appData.loadPositionFile);
1311             /* [HGM] try to make self-starting even after FEN load */
1312             /* to allow automatic setup of fairy variants with wtm */
1313             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1314                 gameMode = BeginningOfGame;
1315                 setboardSpoiledMachineBlack = 1;
1316             }
1317             /* [HGM] loadPos: make that every new game uses the setup */
1318             /* from file as long as we do not switch variant          */
1319             if(!blackPlaysFirst) {
1320                 startedFromPositionFile = TRUE;
1321                 CopyBoard(filePosition, boards[0]);
1322             }
1323         }
1324         if (initialMode == AnalyzeMode) {
1325           if (appData.noChessProgram) {
1326             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1327             return;
1328           }
1329           if (appData.icsActive) {
1330             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1331             return;
1332           }
1333           AnalyzeModeEvent();
1334         } else if (initialMode == AnalyzeFile) {
1335           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1336           ShowThinkingEvent();
1337           AnalyzeFileEvent();
1338           AnalysisPeriodicEvent(1);
1339         } else if (initialMode == MachinePlaysWhite) {
1340           if (appData.noChessProgram) {
1341             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1342                               0, 2);
1343             return;
1344           }
1345           if (appData.icsActive) {
1346             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1347                               0, 2);
1348             return;
1349           }
1350           MachineWhiteEvent();
1351         } else if (initialMode == MachinePlaysBlack) {
1352           if (appData.noChessProgram) {
1353             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1354                               0, 2);
1355             return;
1356           }
1357           if (appData.icsActive) {
1358             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1359                               0, 2);
1360             return;
1361           }
1362           MachineBlackEvent();
1363         } else if (initialMode == TwoMachinesPlay) {
1364           if (appData.noChessProgram) {
1365             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1366                               0, 2);
1367             return;
1368           }
1369           if (appData.icsActive) {
1370             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1371                               0, 2);
1372             return;
1373           }
1374           TwoMachinesEvent();
1375         } else if (initialMode == EditGame) {
1376           EditGameEvent();
1377         } else if (initialMode == EditPosition) {
1378           EditPositionEvent();
1379         } else if (initialMode == Training) {
1380           if (*appData.loadGameFile == NULLCHAR) {
1381             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1382             return;
1383           }
1384           TrainingEvent();
1385         }
1386     }
1387 }
1388
1389 /*
1390  * Establish will establish a contact to a remote host.port.
1391  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1392  *  used to talk to the host.
1393  * Returns 0 if okay, error code if not.
1394  */
1395 int
1396 establish()
1397 {
1398     char buf[MSG_SIZ];
1399
1400     if (*appData.icsCommPort != NULLCHAR) {
1401         /* Talk to the host through a serial comm port */
1402         return OpenCommPort(appData.icsCommPort, &icsPR);
1403
1404     } else if (*appData.gateway != NULLCHAR) {
1405         if (*appData.remoteShell == NULLCHAR) {
1406             /* Use the rcmd protocol to run telnet program on a gateway host */
1407             snprintf(buf, sizeof(buf), "%s %s %s",
1408                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1409             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1410
1411         } else {
1412             /* Use the rsh program to run telnet program on a gateway host */
1413             if (*appData.remoteUser == NULLCHAR) {
1414                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1415                         appData.gateway, appData.telnetProgram,
1416                         appData.icsHost, appData.icsPort);
1417             } else {
1418                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1419                         appData.remoteShell, appData.gateway,
1420                         appData.remoteUser, appData.telnetProgram,
1421                         appData.icsHost, appData.icsPort);
1422             }
1423             return StartChildProcess(buf, "", &icsPR);
1424
1425         }
1426     } else if (appData.useTelnet) {
1427         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1428
1429     } else {
1430         /* TCP socket interface differs somewhat between
1431            Unix and NT; handle details in the front end.
1432            */
1433         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1434     }
1435 }
1436
1437 void EscapeExpand(char *p, char *q)
1438 {       // [HGM] initstring: routine to shape up string arguments
1439         while(*p++ = *q++) if(p[-1] == '\\')
1440             switch(*q++) {
1441                 case 'n': p[-1] = '\n'; break;
1442                 case 'r': p[-1] = '\r'; break;
1443                 case 't': p[-1] = '\t'; break;
1444                 case '\\': p[-1] = '\\'; break;
1445                 case 0: *p = 0; return;
1446                 default: p[-1] = q[-1]; break;
1447             }
1448 }
1449
1450 void
1451 show_bytes(fp, buf, count)
1452      FILE *fp;
1453      char *buf;
1454      int count;
1455 {
1456     while (count--) {
1457         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1458             fprintf(fp, "\\%03o", *buf & 0xff);
1459         } else {
1460             putc(*buf, fp);
1461         }
1462         buf++;
1463     }
1464     fflush(fp);
1465 }
1466
1467 /* Returns an errno value */
1468 int
1469 OutputMaybeTelnet(pr, message, count, outError)
1470      ProcRef pr;
1471      char *message;
1472      int count;
1473      int *outError;
1474 {
1475     char buf[8192], *p, *q, *buflim;
1476     int left, newcount, outcount;
1477
1478     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1479         *appData.gateway != NULLCHAR) {
1480         if (appData.debugMode) {
1481             fprintf(debugFP, ">ICS: ");
1482             show_bytes(debugFP, message, count);
1483             fprintf(debugFP, "\n");
1484         }
1485         return OutputToProcess(pr, message, count, outError);
1486     }
1487
1488     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1489     p = message;
1490     q = buf;
1491     left = count;
1492     newcount = 0;
1493     while (left) {
1494         if (q >= buflim) {
1495             if (appData.debugMode) {
1496                 fprintf(debugFP, ">ICS: ");
1497                 show_bytes(debugFP, buf, newcount);
1498                 fprintf(debugFP, "\n");
1499             }
1500             outcount = OutputToProcess(pr, buf, newcount, outError);
1501             if (outcount < newcount) return -1; /* to be sure */
1502             q = buf;
1503             newcount = 0;
1504         }
1505         if (*p == '\n') {
1506             *q++ = '\r';
1507             newcount++;
1508         } else if (((unsigned char) *p) == TN_IAC) {
1509             *q++ = (char) TN_IAC;
1510             newcount ++;
1511         }
1512         *q++ = *p++;
1513         newcount++;
1514         left--;
1515     }
1516     if (appData.debugMode) {
1517         fprintf(debugFP, ">ICS: ");
1518         show_bytes(debugFP, buf, newcount);
1519         fprintf(debugFP, "\n");
1520     }
1521     outcount = OutputToProcess(pr, buf, newcount, outError);
1522     if (outcount < newcount) return -1; /* to be sure */
1523     return count;
1524 }
1525
1526 void
1527 read_from_player(isr, closure, message, count, error)
1528      InputSourceRef isr;
1529      VOIDSTAR closure;
1530      char *message;
1531      int count;
1532      int error;
1533 {
1534     int outError, outCount;
1535     static int gotEof = 0;
1536
1537     /* Pass data read from player on to ICS */
1538     if (count > 0) {
1539         gotEof = 0;
1540         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1541         if (outCount < count) {
1542             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1543         }
1544     } else if (count < 0) {
1545         RemoveInputSource(isr);
1546         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1547     } else if (gotEof++ > 0) {
1548         RemoveInputSource(isr);
1549         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1550     }
1551 }
1552
1553 void
1554 KeepAlive()
1555 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1556     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1557     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1558     SendToICS("date\n");
1559     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1560 }
1561
1562 /* added routine for printf style output to ics */
1563 void ics_printf(char *format, ...)
1564 {
1565     char buffer[MSG_SIZ];
1566     va_list args;
1567
1568     va_start(args, format);
1569     vsnprintf(buffer, sizeof(buffer), format, args);
1570     buffer[sizeof(buffer)-1] = '\0';
1571     SendToICS(buffer);
1572     va_end(args);
1573 }
1574
1575 void
1576 SendToICS(s)
1577      char *s;
1578 {
1579     int count, outCount, outError;
1580
1581     if (icsPR == NULL) return;
1582
1583     count = strlen(s);
1584     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1585     if (outCount < count) {
1586         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1587     }
1588 }
1589
1590 /* This is used for sending logon scripts to the ICS. Sending
1591    without a delay causes problems when using timestamp on ICC
1592    (at least on my machine). */
1593 void
1594 SendToICSDelayed(s,msdelay)
1595      char *s;
1596      long msdelay;
1597 {
1598     int count, outCount, outError;
1599
1600     if (icsPR == NULL) return;
1601
1602     count = strlen(s);
1603     if (appData.debugMode) {
1604         fprintf(debugFP, ">ICS: ");
1605         show_bytes(debugFP, s, count);
1606         fprintf(debugFP, "\n");
1607     }
1608     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1609                                       msdelay);
1610     if (outCount < count) {
1611         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1612     }
1613 }
1614
1615
1616 /* Remove all highlighting escape sequences in s
1617    Also deletes any suffix starting with '('
1618    */
1619 char *
1620 StripHighlightAndTitle(s)
1621      char *s;
1622 {
1623     static char retbuf[MSG_SIZ];
1624     char *p = retbuf;
1625
1626     while (*s != NULLCHAR) {
1627         while (*s == '\033') {
1628             while (*s != NULLCHAR && !isalpha(*s)) s++;
1629             if (*s != NULLCHAR) s++;
1630         }
1631         while (*s != NULLCHAR && *s != '\033') {
1632             if (*s == '(' || *s == '[') {
1633                 *p = NULLCHAR;
1634                 return retbuf;
1635             }
1636             *p++ = *s++;
1637         }
1638     }
1639     *p = NULLCHAR;
1640     return retbuf;
1641 }
1642
1643 /* Remove all highlighting escape sequences in s */
1644 char *
1645 StripHighlight(s)
1646      char *s;
1647 {
1648     static char retbuf[MSG_SIZ];
1649     char *p = retbuf;
1650
1651     while (*s != NULLCHAR) {
1652         while (*s == '\033') {
1653             while (*s != NULLCHAR && !isalpha(*s)) s++;
1654             if (*s != NULLCHAR) s++;
1655         }
1656         while (*s != NULLCHAR && *s != '\033') {
1657             *p++ = *s++;
1658         }
1659     }
1660     *p = NULLCHAR;
1661     return retbuf;
1662 }
1663
1664 char *variantNames[] = VARIANT_NAMES;
1665 char *
1666 VariantName(v)
1667      VariantClass v;
1668 {
1669     return variantNames[v];
1670 }
1671
1672
1673 /* Identify a variant from the strings the chess servers use or the
1674    PGN Variant tag names we use. */
1675 VariantClass
1676 StringToVariant(e)
1677      char *e;
1678 {
1679     char *p;
1680     int wnum = -1;
1681     VariantClass v = VariantNormal;
1682     int i, found = FALSE;
1683     char buf[MSG_SIZ];
1684     int len;
1685
1686     if (!e) return v;
1687
1688     /* [HGM] skip over optional board-size prefixes */
1689     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1690         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1691         while( *e++ != '_');
1692     }
1693
1694     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1695         v = VariantNormal;
1696         found = TRUE;
1697     } else
1698     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1699       if (StrCaseStr(e, variantNames[i])) {
1700         v = (VariantClass) i;
1701         found = TRUE;
1702         break;
1703       }
1704     }
1705
1706     if (!found) {
1707       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1708           || StrCaseStr(e, "wild/fr")
1709           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1710         v = VariantFischeRandom;
1711       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1712                  (i = 1, p = StrCaseStr(e, "w"))) {
1713         p += i;
1714         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1715         if (isdigit(*p)) {
1716           wnum = atoi(p);
1717         } else {
1718           wnum = -1;
1719         }
1720         switch (wnum) {
1721         case 0: /* FICS only, actually */
1722         case 1:
1723           /* Castling legal even if K starts on d-file */
1724           v = VariantWildCastle;
1725           break;
1726         case 2:
1727         case 3:
1728         case 4:
1729           /* Castling illegal even if K & R happen to start in
1730              normal positions. */
1731           v = VariantNoCastle;
1732           break;
1733         case 5:
1734         case 7:
1735         case 8:
1736         case 10:
1737         case 11:
1738         case 12:
1739         case 13:
1740         case 14:
1741         case 15:
1742         case 18:
1743         case 19:
1744           /* Castling legal iff K & R start in normal positions */
1745           v = VariantNormal;
1746           break;
1747         case 6:
1748         case 20:
1749         case 21:
1750           /* Special wilds for position setup; unclear what to do here */
1751           v = VariantLoadable;
1752           break;
1753         case 9:
1754           /* Bizarre ICC game */
1755           v = VariantTwoKings;
1756           break;
1757         case 16:
1758           v = VariantKriegspiel;
1759           break;
1760         case 17:
1761           v = VariantLosers;
1762           break;
1763         case 22:
1764           v = VariantFischeRandom;
1765           break;
1766         case 23:
1767           v = VariantCrazyhouse;
1768           break;
1769         case 24:
1770           v = VariantBughouse;
1771           break;
1772         case 25:
1773           v = Variant3Check;
1774           break;
1775         case 26:
1776           /* Not quite the same as FICS suicide! */
1777           v = VariantGiveaway;
1778           break;
1779         case 27:
1780           v = VariantAtomic;
1781           break;
1782         case 28:
1783           v = VariantShatranj;
1784           break;
1785
1786         /* Temporary names for future ICC types.  The name *will* change in
1787            the next xboard/WinBoard release after ICC defines it. */
1788         case 29:
1789           v = Variant29;
1790           break;
1791         case 30:
1792           v = Variant30;
1793           break;
1794         case 31:
1795           v = Variant31;
1796           break;
1797         case 32:
1798           v = Variant32;
1799           break;
1800         case 33:
1801           v = Variant33;
1802           break;
1803         case 34:
1804           v = Variant34;
1805           break;
1806         case 35:
1807           v = Variant35;
1808           break;
1809         case 36:
1810           v = Variant36;
1811           break;
1812         case 37:
1813           v = VariantShogi;
1814           break;
1815         case 38:
1816           v = VariantXiangqi;
1817           break;
1818         case 39:
1819           v = VariantCourier;
1820           break;
1821         case 40:
1822           v = VariantGothic;
1823           break;
1824         case 41:
1825           v = VariantCapablanca;
1826           break;
1827         case 42:
1828           v = VariantKnightmate;
1829           break;
1830         case 43:
1831           v = VariantFairy;
1832           break;
1833         case 44:
1834           v = VariantCylinder;
1835           break;
1836         case 45:
1837           v = VariantFalcon;
1838           break;
1839         case 46:
1840           v = VariantCapaRandom;
1841           break;
1842         case 47:
1843           v = VariantBerolina;
1844           break;
1845         case 48:
1846           v = VariantJanus;
1847           break;
1848         case 49:
1849           v = VariantSuper;
1850           break;
1851         case 50:
1852           v = VariantGreat;
1853           break;
1854         case -1:
1855           /* Found "wild" or "w" in the string but no number;
1856              must assume it's normal chess. */
1857           v = VariantNormal;
1858           break;
1859         default:
1860           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1861           if( (len > MSG_SIZ) && appData.debugMode )
1862             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1863
1864           DisplayError(buf, 0);
1865           v = VariantUnknown;
1866           break;
1867         }
1868       }
1869     }
1870     if (appData.debugMode) {
1871       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1872               e, wnum, VariantName(v));
1873     }
1874     return v;
1875 }
1876
1877 static int leftover_start = 0, leftover_len = 0;
1878 char star_match[STAR_MATCH_N][MSG_SIZ];
1879
1880 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1881    advance *index beyond it, and set leftover_start to the new value of
1882    *index; else return FALSE.  If pattern contains the character '*', it
1883    matches any sequence of characters not containing '\r', '\n', or the
1884    character following the '*' (if any), and the matched sequence(s) are
1885    copied into star_match.
1886    */
1887 int
1888 looking_at(buf, index, pattern)
1889      char *buf;
1890      int *index;
1891      char *pattern;
1892 {
1893     char *bufp = &buf[*index], *patternp = pattern;
1894     int star_count = 0;
1895     char *matchp = star_match[0];
1896
1897     for (;;) {
1898         if (*patternp == NULLCHAR) {
1899             *index = leftover_start = bufp - buf;
1900             *matchp = NULLCHAR;
1901             return TRUE;
1902         }
1903         if (*bufp == NULLCHAR) return FALSE;
1904         if (*patternp == '*') {
1905             if (*bufp == *(patternp + 1)) {
1906                 *matchp = NULLCHAR;
1907                 matchp = star_match[++star_count];
1908                 patternp += 2;
1909                 bufp++;
1910                 continue;
1911             } else if (*bufp == '\n' || *bufp == '\r') {
1912                 patternp++;
1913                 if (*patternp == NULLCHAR)
1914                   continue;
1915                 else
1916                   return FALSE;
1917             } else {
1918                 *matchp++ = *bufp++;
1919                 continue;
1920             }
1921         }
1922         if (*patternp != *bufp) return FALSE;
1923         patternp++;
1924         bufp++;
1925     }
1926 }
1927
1928 void
1929 SendToPlayer(data, length)
1930      char *data;
1931      int length;
1932 {
1933     int error, outCount;
1934     outCount = OutputToProcess(NoProc, data, length, &error);
1935     if (outCount < length) {
1936         DisplayFatalError(_("Error writing to display"), error, 1);
1937     }
1938 }
1939
1940 void
1941 PackHolding(packed, holding)
1942      char packed[];
1943      char *holding;
1944 {
1945     char *p = holding;
1946     char *q = packed;
1947     int runlength = 0;
1948     int curr = 9999;
1949     do {
1950         if (*p == curr) {
1951             runlength++;
1952         } else {
1953             switch (runlength) {
1954               case 0:
1955                 break;
1956               case 1:
1957                 *q++ = curr;
1958                 break;
1959               case 2:
1960                 *q++ = curr;
1961                 *q++ = curr;
1962                 break;
1963               default:
1964                 sprintf(q, "%d", runlength);
1965                 while (*q) q++;
1966                 *q++ = curr;
1967                 break;
1968             }
1969             runlength = 1;
1970             curr = *p;
1971         }
1972     } while (*p++);
1973     *q = NULLCHAR;
1974 }
1975
1976 /* Telnet protocol requests from the front end */
1977 void
1978 TelnetRequest(ddww, option)
1979      unsigned char ddww, option;
1980 {
1981     unsigned char msg[3];
1982     int outCount, outError;
1983
1984     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1985
1986     if (appData.debugMode) {
1987         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1988         switch (ddww) {
1989           case TN_DO:
1990             ddwwStr = "DO";
1991             break;
1992           case TN_DONT:
1993             ddwwStr = "DONT";
1994             break;
1995           case TN_WILL:
1996             ddwwStr = "WILL";
1997             break;
1998           case TN_WONT:
1999             ddwwStr = "WONT";
2000             break;
2001           default:
2002             ddwwStr = buf1;
2003             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2004             break;
2005         }
2006         switch (option) {
2007           case TN_ECHO:
2008             optionStr = "ECHO";
2009             break;
2010           default:
2011             optionStr = buf2;
2012             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2013             break;
2014         }
2015         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2016     }
2017     msg[0] = TN_IAC;
2018     msg[1] = ddww;
2019     msg[2] = option;
2020     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2021     if (outCount < 3) {
2022         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2023     }
2024 }
2025
2026 void
2027 DoEcho()
2028 {
2029     if (!appData.icsActive) return;
2030     TelnetRequest(TN_DO, TN_ECHO);
2031 }
2032
2033 void
2034 DontEcho()
2035 {
2036     if (!appData.icsActive) return;
2037     TelnetRequest(TN_DONT, TN_ECHO);
2038 }
2039
2040 void
2041 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2042 {
2043     /* put the holdings sent to us by the server on the board holdings area */
2044     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2045     char p;
2046     ChessSquare piece;
2047
2048     if(gameInfo.holdingsWidth < 2)  return;
2049     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2050         return; // prevent overwriting by pre-board holdings
2051
2052     if( (int)lowestPiece >= BlackPawn ) {
2053         holdingsColumn = 0;
2054         countsColumn = 1;
2055         holdingsStartRow = BOARD_HEIGHT-1;
2056         direction = -1;
2057     } else {
2058         holdingsColumn = BOARD_WIDTH-1;
2059         countsColumn = BOARD_WIDTH-2;
2060         holdingsStartRow = 0;
2061         direction = 1;
2062     }
2063
2064     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2065         board[i][holdingsColumn] = EmptySquare;
2066         board[i][countsColumn]   = (ChessSquare) 0;
2067     }
2068     while( (p=*holdings++) != NULLCHAR ) {
2069         piece = CharToPiece( ToUpper(p) );
2070         if(piece == EmptySquare) continue;
2071         /*j = (int) piece - (int) WhitePawn;*/
2072         j = PieceToNumber(piece);
2073         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2074         if(j < 0) continue;               /* should not happen */
2075         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2076         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2077         board[holdingsStartRow+j*direction][countsColumn]++;
2078     }
2079 }
2080
2081
2082 void
2083 VariantSwitch(Board board, VariantClass newVariant)
2084 {
2085    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2086    static Board oldBoard;
2087
2088    startedFromPositionFile = FALSE;
2089    if(gameInfo.variant == newVariant) return;
2090
2091    /* [HGM] This routine is called each time an assignment is made to
2092     * gameInfo.variant during a game, to make sure the board sizes
2093     * are set to match the new variant. If that means adding or deleting
2094     * holdings, we shift the playing board accordingly
2095     * This kludge is needed because in ICS observe mode, we get boards
2096     * of an ongoing game without knowing the variant, and learn about the
2097     * latter only later. This can be because of the move list we requested,
2098     * in which case the game history is refilled from the beginning anyway,
2099     * but also when receiving holdings of a crazyhouse game. In the latter
2100     * case we want to add those holdings to the already received position.
2101     */
2102
2103
2104    if (appData.debugMode) {
2105      fprintf(debugFP, "Switch board from %s to %s\n",
2106              VariantName(gameInfo.variant), VariantName(newVariant));
2107      setbuf(debugFP, NULL);
2108    }
2109    shuffleOpenings = 0;       /* [HGM] shuffle */
2110    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2111    switch(newVariant)
2112      {
2113      case VariantShogi:
2114        newWidth = 9;  newHeight = 9;
2115        gameInfo.holdingsSize = 7;
2116      case VariantBughouse:
2117      case VariantCrazyhouse:
2118        newHoldingsWidth = 2; break;
2119      case VariantGreat:
2120        newWidth = 10;
2121      case VariantSuper:
2122        newHoldingsWidth = 2;
2123        gameInfo.holdingsSize = 8;
2124        break;
2125      case VariantGothic:
2126      case VariantCapablanca:
2127      case VariantCapaRandom:
2128        newWidth = 10;
2129      default:
2130        newHoldingsWidth = gameInfo.holdingsSize = 0;
2131      };
2132
2133    if(newWidth  != gameInfo.boardWidth  ||
2134       newHeight != gameInfo.boardHeight ||
2135       newHoldingsWidth != gameInfo.holdingsWidth ) {
2136
2137      /* shift position to new playing area, if needed */
2138      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2139        for(i=0; i<BOARD_HEIGHT; i++)
2140          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2141            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2142              board[i][j];
2143        for(i=0; i<newHeight; i++) {
2144          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2145          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2146        }
2147      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2148        for(i=0; i<BOARD_HEIGHT; i++)
2149          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2150            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2151              board[i][j];
2152      }
2153      gameInfo.boardWidth  = newWidth;
2154      gameInfo.boardHeight = newHeight;
2155      gameInfo.holdingsWidth = newHoldingsWidth;
2156      gameInfo.variant = newVariant;
2157      InitDrawingSizes(-2, 0);
2158    } else gameInfo.variant = newVariant;
2159    CopyBoard(oldBoard, board);   // remember correctly formatted board
2160      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2161    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2162 }
2163
2164 static int loggedOn = FALSE;
2165
2166 /*-- Game start info cache: --*/
2167 int gs_gamenum;
2168 char gs_kind[MSG_SIZ];
2169 static char player1Name[128] = "";
2170 static char player2Name[128] = "";
2171 static char cont_seq[] = "\n\\   ";
2172 static int player1Rating = -1;
2173 static int player2Rating = -1;
2174 /*----------------------------*/
2175
2176 ColorClass curColor = ColorNormal;
2177 int suppressKibitz = 0;
2178
2179 // [HGM] seekgraph
2180 Boolean soughtPending = FALSE;
2181 Boolean seekGraphUp;
2182 #define MAX_SEEK_ADS 200
2183 #define SQUARE 0x80
2184 char *seekAdList[MAX_SEEK_ADS];
2185 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2186 float tcList[MAX_SEEK_ADS];
2187 char colorList[MAX_SEEK_ADS];
2188 int nrOfSeekAds = 0;
2189 int minRating = 1010, maxRating = 2800;
2190 int hMargin = 10, vMargin = 20, h, w;
2191 extern int squareSize, lineGap;
2192
2193 void
2194 PlotSeekAd(int i)
2195 {
2196         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2197         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2198         if(r < minRating+100 && r >=0 ) r = minRating+100;
2199         if(r > maxRating) r = maxRating;
2200         if(tc < 1.) tc = 1.;
2201         if(tc > 95.) tc = 95.;
2202         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2203         y = ((double)r - minRating)/(maxRating - minRating)
2204             * (h-vMargin-squareSize/8-1) + vMargin;
2205         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2206         if(strstr(seekAdList[i], " u ")) color = 1;
2207         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2208            !strstr(seekAdList[i], "bullet") &&
2209            !strstr(seekAdList[i], "blitz") &&
2210            !strstr(seekAdList[i], "standard") ) color = 2;
2211         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2212         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2213 }
2214
2215 void
2216 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2217 {
2218         char buf[MSG_SIZ], *ext = "";
2219         VariantClass v = StringToVariant(type);
2220         if(strstr(type, "wild")) {
2221             ext = type + 4; // append wild number
2222             if(v == VariantFischeRandom) type = "chess960"; else
2223             if(v == VariantLoadable) type = "setup"; else
2224             type = VariantName(v);
2225         }
2226         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2227         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2228             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2229             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2230             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2231             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2232             seekNrList[nrOfSeekAds] = nr;
2233             zList[nrOfSeekAds] = 0;
2234             seekAdList[nrOfSeekAds++] = StrSave(buf);
2235             if(plot) PlotSeekAd(nrOfSeekAds-1);
2236         }
2237 }
2238
2239 void
2240 EraseSeekDot(int i)
2241 {
2242     int x = xList[i], y = yList[i], d=squareSize/4, k;
2243     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2244     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2245     // now replot every dot that overlapped
2246     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2247         int xx = xList[k], yy = yList[k];
2248         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2249             DrawSeekDot(xx, yy, colorList[k]);
2250     }
2251 }
2252
2253 void
2254 RemoveSeekAd(int nr)
2255 {
2256         int i;
2257         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2258             EraseSeekDot(i);
2259             if(seekAdList[i]) free(seekAdList[i]);
2260             seekAdList[i] = seekAdList[--nrOfSeekAds];
2261             seekNrList[i] = seekNrList[nrOfSeekAds];
2262             ratingList[i] = ratingList[nrOfSeekAds];
2263             colorList[i]  = colorList[nrOfSeekAds];
2264             tcList[i] = tcList[nrOfSeekAds];
2265             xList[i]  = xList[nrOfSeekAds];
2266             yList[i]  = yList[nrOfSeekAds];
2267             zList[i]  = zList[nrOfSeekAds];
2268             seekAdList[nrOfSeekAds] = NULL;
2269             break;
2270         }
2271 }
2272
2273 Boolean
2274 MatchSoughtLine(char *line)
2275 {
2276     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2277     int nr, base, inc, u=0; char dummy;
2278
2279     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2280        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2281        (u=1) &&
2282        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2283         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2284         // match: compact and save the line
2285         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2286         return TRUE;
2287     }
2288     return FALSE;
2289 }
2290
2291 int
2292 DrawSeekGraph()
2293 {
2294     int i;
2295     if(!seekGraphUp) return FALSE;
2296     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2297     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2298
2299     DrawSeekBackground(0, 0, w, h);
2300     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2301     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2302     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2303         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2304         yy = h-1-yy;
2305         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2306         if(i%500 == 0) {
2307             char buf[MSG_SIZ];
2308             snprintf(buf, MSG_SIZ, "%d", i);
2309             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2310         }
2311     }
2312     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2313     for(i=1; i<100; i+=(i<10?1:5)) {
2314         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2315         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2316         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2317             char buf[MSG_SIZ];
2318             snprintf(buf, MSG_SIZ, "%d", i);
2319             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2320         }
2321     }
2322     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2323     return TRUE;
2324 }
2325
2326 int SeekGraphClick(ClickType click, int x, int y, int moving)
2327 {
2328     static int lastDown = 0, displayed = 0, lastSecond;
2329     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2330         if(click == Release || moving) return FALSE;
2331         nrOfSeekAds = 0;
2332         soughtPending = TRUE;
2333         SendToICS(ics_prefix);
2334         SendToICS("sought\n"); // should this be "sought all"?
2335     } else { // issue challenge based on clicked ad
2336         int dist = 10000; int i, closest = 0, second = 0;
2337         for(i=0; i<nrOfSeekAds; i++) {
2338             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2339             if(d < dist) { dist = d; closest = i; }
2340             second += (d - zList[i] < 120); // count in-range ads
2341             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2342         }
2343         if(dist < 120) {
2344             char buf[MSG_SIZ];
2345             second = (second > 1);
2346             if(displayed != closest || second != lastSecond) {
2347                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2348                 lastSecond = second; displayed = closest;
2349             }
2350             if(click == Press) {
2351                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2352                 lastDown = closest;
2353                 return TRUE;
2354             } // on press 'hit', only show info
2355             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2356             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2357             SendToICS(ics_prefix);
2358             SendToICS(buf);
2359             return TRUE; // let incoming board of started game pop down the graph
2360         } else if(click == Release) { // release 'miss' is ignored
2361             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2362             if(moving == 2) { // right up-click
2363                 nrOfSeekAds = 0; // refresh graph
2364                 soughtPending = TRUE;
2365                 SendToICS(ics_prefix);
2366                 SendToICS("sought\n"); // should this be "sought all"?
2367             }
2368             return TRUE;
2369         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2370         // press miss or release hit 'pop down' seek graph
2371         seekGraphUp = FALSE;
2372         DrawPosition(TRUE, NULL);
2373     }
2374     return TRUE;
2375 }
2376
2377 void
2378 read_from_ics(isr, closure, data, count, error)
2379      InputSourceRef isr;
2380      VOIDSTAR closure;
2381      char *data;
2382      int count;
2383      int error;
2384 {
2385 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2386 #define STARTED_NONE 0
2387 #define STARTED_MOVES 1
2388 #define STARTED_BOARD 2
2389 #define STARTED_OBSERVE 3
2390 #define STARTED_HOLDINGS 4
2391 #define STARTED_CHATTER 5
2392 #define STARTED_COMMENT 6
2393 #define STARTED_MOVES_NOHIDE 7
2394
2395     static int started = STARTED_NONE;
2396     static char parse[20000];
2397     static int parse_pos = 0;
2398     static char buf[BUF_SIZE + 1];
2399     static int firstTime = TRUE, intfSet = FALSE;
2400     static ColorClass prevColor = ColorNormal;
2401     static int savingComment = FALSE;
2402     static int cmatch = 0; // continuation sequence match
2403     char *bp;
2404     char str[MSG_SIZ];
2405     int i, oldi;
2406     int buf_len;
2407     int next_out;
2408     int tkind;
2409     int backup;    /* [DM] For zippy color lines */
2410     char *p;
2411     char talker[MSG_SIZ]; // [HGM] chat
2412     int channel;
2413
2414     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2415
2416     if (appData.debugMode) {
2417       if (!error) {
2418         fprintf(debugFP, "<ICS: ");
2419         show_bytes(debugFP, data, count);
2420         fprintf(debugFP, "\n");
2421       }
2422     }
2423
2424     if (appData.debugMode) { int f = forwardMostMove;
2425         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2426                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2427                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2428     }
2429     if (count > 0) {
2430         /* If last read ended with a partial line that we couldn't parse,
2431            prepend it to the new read and try again. */
2432         if (leftover_len > 0) {
2433             for (i=0; i<leftover_len; i++)
2434               buf[i] = buf[leftover_start + i];
2435         }
2436
2437     /* copy new characters into the buffer */
2438     bp = buf + leftover_len;
2439     buf_len=leftover_len;
2440     for (i=0; i<count; i++)
2441     {
2442         // ignore these
2443         if (data[i] == '\r')
2444             continue;
2445
2446         // join lines split by ICS?
2447         if (!appData.noJoin)
2448         {
2449             /*
2450                 Joining just consists of finding matches against the
2451                 continuation sequence, and discarding that sequence
2452                 if found instead of copying it.  So, until a match
2453                 fails, there's nothing to do since it might be the
2454                 complete sequence, and thus, something we don't want
2455                 copied.
2456             */
2457             if (data[i] == cont_seq[cmatch])
2458             {
2459                 cmatch++;
2460                 if (cmatch == strlen(cont_seq))
2461                 {
2462                     cmatch = 0; // complete match.  just reset the counter
2463
2464                     /*
2465                         it's possible for the ICS to not include the space
2466                         at the end of the last word, making our [correct]
2467                         join operation fuse two separate words.  the server
2468                         does this when the space occurs at the width setting.
2469                     */
2470                     if (!buf_len || buf[buf_len-1] != ' ')
2471                     {
2472                         *bp++ = ' ';
2473                         buf_len++;
2474                     }
2475                 }
2476                 continue;
2477             }
2478             else if (cmatch)
2479             {
2480                 /*
2481                     match failed, so we have to copy what matched before
2482                     falling through and copying this character.  In reality,
2483                     this will only ever be just the newline character, but
2484                     it doesn't hurt to be precise.
2485                 */
2486                 strncpy(bp, cont_seq, cmatch);
2487                 bp += cmatch;
2488                 buf_len += cmatch;
2489                 cmatch = 0;
2490             }
2491         }
2492
2493         // copy this char
2494         *bp++ = data[i];
2495         buf_len++;
2496     }
2497
2498         buf[buf_len] = NULLCHAR;
2499 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2500         next_out = 0;
2501         leftover_start = 0;
2502
2503         i = 0;
2504         while (i < buf_len) {
2505             /* Deal with part of the TELNET option negotiation
2506                protocol.  We refuse to do anything beyond the
2507                defaults, except that we allow the WILL ECHO option,
2508                which ICS uses to turn off password echoing when we are
2509                directly connected to it.  We reject this option
2510                if localLineEditing mode is on (always on in xboard)
2511                and we are talking to port 23, which might be a real
2512                telnet server that will try to keep WILL ECHO on permanently.
2513              */
2514             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2515                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2516                 unsigned char option;
2517                 oldi = i;
2518                 switch ((unsigned char) buf[++i]) {
2519                   case TN_WILL:
2520                     if (appData.debugMode)
2521                       fprintf(debugFP, "\n<WILL ");
2522                     switch (option = (unsigned char) buf[++i]) {
2523                       case TN_ECHO:
2524                         if (appData.debugMode)
2525                           fprintf(debugFP, "ECHO ");
2526                         /* Reply only if this is a change, according
2527                            to the protocol rules. */
2528                         if (remoteEchoOption) break;
2529                         if (appData.localLineEditing &&
2530                             atoi(appData.icsPort) == TN_PORT) {
2531                             TelnetRequest(TN_DONT, TN_ECHO);
2532                         } else {
2533                             EchoOff();
2534                             TelnetRequest(TN_DO, TN_ECHO);
2535                             remoteEchoOption = TRUE;
2536                         }
2537                         break;
2538                       default:
2539                         if (appData.debugMode)
2540                           fprintf(debugFP, "%d ", option);
2541                         /* Whatever this is, we don't want it. */
2542                         TelnetRequest(TN_DONT, option);
2543                         break;
2544                     }
2545                     break;
2546                   case TN_WONT:
2547                     if (appData.debugMode)
2548                       fprintf(debugFP, "\n<WONT ");
2549                     switch (option = (unsigned char) buf[++i]) {
2550                       case TN_ECHO:
2551                         if (appData.debugMode)
2552                           fprintf(debugFP, "ECHO ");
2553                         /* Reply only if this is a change, according
2554                            to the protocol rules. */
2555                         if (!remoteEchoOption) break;
2556                         EchoOn();
2557                         TelnetRequest(TN_DONT, TN_ECHO);
2558                         remoteEchoOption = FALSE;
2559                         break;
2560                       default:
2561                         if (appData.debugMode)
2562                           fprintf(debugFP, "%d ", (unsigned char) option);
2563                         /* Whatever this is, it must already be turned
2564                            off, because we never agree to turn on
2565                            anything non-default, so according to the
2566                            protocol rules, we don't reply. */
2567                         break;
2568                     }
2569                     break;
2570                   case TN_DO:
2571                     if (appData.debugMode)
2572                       fprintf(debugFP, "\n<DO ");
2573                     switch (option = (unsigned char) buf[++i]) {
2574                       default:
2575                         /* Whatever this is, we refuse to do it. */
2576                         if (appData.debugMode)
2577                           fprintf(debugFP, "%d ", option);
2578                         TelnetRequest(TN_WONT, option);
2579                         break;
2580                     }
2581                     break;
2582                   case TN_DONT:
2583                     if (appData.debugMode)
2584                       fprintf(debugFP, "\n<DONT ");
2585                     switch (option = (unsigned char) buf[++i]) {
2586                       default:
2587                         if (appData.debugMode)
2588                           fprintf(debugFP, "%d ", option);
2589                         /* Whatever this is, we are already not doing
2590                            it, because we never agree to do anything
2591                            non-default, so according to the protocol
2592                            rules, we don't reply. */
2593                         break;
2594                     }
2595                     break;
2596                   case TN_IAC:
2597                     if (appData.debugMode)
2598                       fprintf(debugFP, "\n<IAC ");
2599                     /* Doubled IAC; pass it through */
2600                     i--;
2601                     break;
2602                   default:
2603                     if (appData.debugMode)
2604                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2605                     /* Drop all other telnet commands on the floor */
2606                     break;
2607                 }
2608                 if (oldi > next_out)
2609                   SendToPlayer(&buf[next_out], oldi - next_out);
2610                 if (++i > next_out)
2611                   next_out = i;
2612                 continue;
2613             }
2614
2615             /* OK, this at least will *usually* work */
2616             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2617                 loggedOn = TRUE;
2618             }
2619
2620             if (loggedOn && !intfSet) {
2621                 if (ics_type == ICS_ICC) {
2622                   snprintf(str, MSG_SIZ,
2623                           "/set-quietly interface %s\n/set-quietly style 12\n",
2624                           programVersion);
2625                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2626                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2627                 } else if (ics_type == ICS_CHESSNET) {
2628                   snprintf(str, MSG_SIZ, "/style 12\n");
2629                 } else {
2630                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2631                   strcat(str, programVersion);
2632                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2633                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2634                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2635 #ifdef WIN32
2636                   strcat(str, "$iset nohighlight 1\n");
2637 #endif
2638                   strcat(str, "$iset lock 1\n$style 12\n");
2639                 }
2640                 SendToICS(str);
2641                 NotifyFrontendLogin();
2642                 intfSet = TRUE;
2643             }
2644
2645             if (started == STARTED_COMMENT) {
2646                 /* Accumulate characters in comment */
2647                 parse[parse_pos++] = buf[i];
2648                 if (buf[i] == '\n') {
2649                     parse[parse_pos] = NULLCHAR;
2650                     if(chattingPartner>=0) {
2651                         char mess[MSG_SIZ];
2652                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2653                         OutputChatMessage(chattingPartner, mess);
2654                         chattingPartner = -1;
2655                         next_out = i+1; // [HGM] suppress printing in ICS window
2656                     } else
2657                     if(!suppressKibitz) // [HGM] kibitz
2658                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2659                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2660                         int nrDigit = 0, nrAlph = 0, j;
2661                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2662                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2663                         parse[parse_pos] = NULLCHAR;
2664                         // try to be smart: if it does not look like search info, it should go to
2665                         // ICS interaction window after all, not to engine-output window.
2666                         for(j=0; j<parse_pos; j++) { // count letters and digits
2667                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2668                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2669                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2670                         }
2671                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2672                             int depth=0; float score;
2673                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2674                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2675                                 pvInfoList[forwardMostMove-1].depth = depth;
2676                                 pvInfoList[forwardMostMove-1].score = 100*score;
2677                             }
2678                             OutputKibitz(suppressKibitz, parse);
2679                         } else {
2680                             char tmp[MSG_SIZ];
2681                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2682                             SendToPlayer(tmp, strlen(tmp));
2683                         }
2684                         next_out = i+1; // [HGM] suppress printing in ICS window
2685                     }
2686                     started = STARTED_NONE;
2687                 } else {
2688                     /* Don't match patterns against characters in comment */
2689                     i++;
2690                     continue;
2691                 }
2692             }
2693             if (started == STARTED_CHATTER) {
2694                 if (buf[i] != '\n') {
2695                     /* Don't match patterns against characters in chatter */
2696                     i++;
2697                     continue;
2698                 }
2699                 started = STARTED_NONE;
2700                 if(suppressKibitz) next_out = i+1;
2701             }
2702
2703             /* Kludge to deal with rcmd protocol */
2704             if (firstTime && looking_at(buf, &i, "\001*")) {
2705                 DisplayFatalError(&buf[1], 0, 1);
2706                 continue;
2707             } else {
2708                 firstTime = FALSE;
2709             }
2710
2711             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2712                 ics_type = ICS_ICC;
2713                 ics_prefix = "/";
2714                 if (appData.debugMode)
2715                   fprintf(debugFP, "ics_type %d\n", ics_type);
2716                 continue;
2717             }
2718             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2719                 ics_type = ICS_FICS;
2720                 ics_prefix = "$";
2721                 if (appData.debugMode)
2722                   fprintf(debugFP, "ics_type %d\n", ics_type);
2723                 continue;
2724             }
2725             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2726                 ics_type = ICS_CHESSNET;
2727                 ics_prefix = "/";
2728                 if (appData.debugMode)
2729                   fprintf(debugFP, "ics_type %d\n", ics_type);
2730                 continue;
2731             }
2732
2733             if (!loggedOn &&
2734                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2735                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2736                  looking_at(buf, &i, "will be \"*\""))) {
2737               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2738               continue;
2739             }
2740
2741             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2742               char buf[MSG_SIZ];
2743               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2744               DisplayIcsInteractionTitle(buf);
2745               have_set_title = TRUE;
2746             }
2747
2748             /* skip finger notes */
2749             if (started == STARTED_NONE &&
2750                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2751                  (buf[i] == '1' && buf[i+1] == '0')) &&
2752                 buf[i+2] == ':' && buf[i+3] == ' ') {
2753               started = STARTED_CHATTER;
2754               i += 3;
2755               continue;
2756             }
2757
2758             oldi = i;
2759             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2760             if(appData.seekGraph) {
2761                 if(soughtPending && MatchSoughtLine(buf+i)) {
2762                     i = strstr(buf+i, "rated") - buf;
2763                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2764                     next_out = leftover_start = i;
2765                     started = STARTED_CHATTER;
2766                     suppressKibitz = TRUE;
2767                     continue;
2768                 }
2769                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2770                         && looking_at(buf, &i, "* ads displayed")) {
2771                     soughtPending = FALSE;
2772                     seekGraphUp = TRUE;
2773                     DrawSeekGraph();
2774                     continue;
2775                 }
2776                 if(appData.autoRefresh) {
2777                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2778                         int s = (ics_type == ICS_ICC); // ICC format differs
2779                         if(seekGraphUp)
2780                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2781                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2782                         looking_at(buf, &i, "*% "); // eat prompt
2783                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2784                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2785                         next_out = i; // suppress
2786                         continue;
2787                     }
2788                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2789                         char *p = star_match[0];
2790                         while(*p) {
2791                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2792                             while(*p && *p++ != ' '); // next
2793                         }
2794                         looking_at(buf, &i, "*% "); // eat prompt
2795                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2796                         next_out = i;
2797                         continue;
2798                     }
2799                 }
2800             }
2801
2802             /* skip formula vars */
2803             if (started == STARTED_NONE &&
2804                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2805               started = STARTED_CHATTER;
2806               i += 3;
2807               continue;
2808             }
2809
2810             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2811             if (appData.autoKibitz && started == STARTED_NONE &&
2812                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2813                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2814                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2815                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2816                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2817                         suppressKibitz = TRUE;
2818                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2819                         next_out = i;
2820                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2821                                 && (gameMode == IcsPlayingWhite)) ||
2822                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2823                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2824                             started = STARTED_CHATTER; // own kibitz we simply discard
2825                         else {
2826                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2827                             parse_pos = 0; parse[0] = NULLCHAR;
2828                             savingComment = TRUE;
2829                             suppressKibitz = gameMode != IcsObserving ? 2 :
2830                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2831                         }
2832                         continue;
2833                 } else
2834                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2835                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2836                          && atoi(star_match[0])) {
2837                     // suppress the acknowledgements of our own autoKibitz
2838                     char *p;
2839                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2840                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2841                     SendToPlayer(star_match[0], strlen(star_match[0]));
2842                     if(looking_at(buf, &i, "*% ")) // eat prompt
2843                         suppressKibitz = FALSE;
2844                     next_out = i;
2845                     continue;
2846                 }
2847             } // [HGM] kibitz: end of patch
2848
2849             // [HGM] chat: intercept tells by users for which we have an open chat window
2850             channel = -1;
2851             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2852                                            looking_at(buf, &i, "* whispers:") ||
2853                                            looking_at(buf, &i, "* kibitzes:") ||
2854                                            looking_at(buf, &i, "* shouts:") ||
2855                                            looking_at(buf, &i, "* c-shouts:") ||
2856                                            looking_at(buf, &i, "--> * ") ||
2857                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2858                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2859                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2860                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2861                 int p;
2862                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2863                 chattingPartner = -1;
2864
2865                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2866                 for(p=0; p<MAX_CHAT; p++) {
2867                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2868                     talker[0] = '['; strcat(talker, "] ");
2869                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2870                     chattingPartner = p; break;
2871                     }
2872                 } else
2873                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2874                 for(p=0; p<MAX_CHAT; p++) {
2875                     if(!strcmp("kibitzes", chatPartner[p])) {
2876                         talker[0] = '['; strcat(talker, "] ");
2877                         chattingPartner = p; break;
2878                     }
2879                 } else
2880                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2881                 for(p=0; p<MAX_CHAT; p++) {
2882                     if(!strcmp("whispers", chatPartner[p])) {
2883                         talker[0] = '['; strcat(talker, "] ");
2884                         chattingPartner = p; break;
2885                     }
2886                 } else
2887                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2888                   if(buf[i-8] == '-' && buf[i-3] == 't')
2889                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2890                     if(!strcmp("c-shouts", chatPartner[p])) {
2891                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2892                         chattingPartner = p; break;
2893                     }
2894                   }
2895                   if(chattingPartner < 0)
2896                   for(p=0; p<MAX_CHAT; p++) {
2897                     if(!strcmp("shouts", chatPartner[p])) {
2898                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2899                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2900                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2901                         chattingPartner = p; break;
2902                     }
2903                   }
2904                 }
2905                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2906                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2907                     talker[0] = 0; Colorize(ColorTell, FALSE);
2908                     chattingPartner = p; break;
2909                 }
2910                 if(chattingPartner<0) i = oldi; else {
2911                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2912                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2913                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2914                     started = STARTED_COMMENT;
2915                     parse_pos = 0; parse[0] = NULLCHAR;
2916                     savingComment = 3 + chattingPartner; // counts as TRUE
2917                     suppressKibitz = TRUE;
2918                     continue;
2919                 }
2920             } // [HGM] chat: end of patch
2921
2922             if (appData.zippyTalk || appData.zippyPlay) {
2923                 /* [DM] Backup address for color zippy lines */
2924                 backup = i;
2925 #if ZIPPY
2926                if (loggedOn == TRUE)
2927                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2928                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2929 #endif
2930             } // [DM] 'else { ' deleted
2931                 if (
2932                     /* Regular tells and says */
2933                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2934                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2935                     looking_at(buf, &i, "* says: ") ||
2936                     /* Don't color "message" or "messages" output */
2937                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2938                     looking_at(buf, &i, "*. * at *:*: ") ||
2939                     looking_at(buf, &i, "--* (*:*): ") ||
2940                     /* Message notifications (same color as tells) */
2941                     looking_at(buf, &i, "* has left a message ") ||
2942                     looking_at(buf, &i, "* just sent you a message:\n") ||
2943                     /* Whispers and kibitzes */
2944                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2945                     looking_at(buf, &i, "* kibitzes: ") ||
2946                     /* Channel tells */
2947                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2948
2949                   if (tkind == 1 && strchr(star_match[0], ':')) {
2950                       /* Avoid "tells you:" spoofs in channels */
2951                      tkind = 3;
2952                   }
2953                   if (star_match[0][0] == NULLCHAR ||
2954                       strchr(star_match[0], ' ') ||
2955                       (tkind == 3 && strchr(star_match[1], ' '))) {
2956                     /* Reject bogus matches */
2957                     i = oldi;
2958                   } else {
2959                     if (appData.colorize) {
2960                       if (oldi > next_out) {
2961                         SendToPlayer(&buf[next_out], oldi - next_out);
2962                         next_out = oldi;
2963                       }
2964                       switch (tkind) {
2965                       case 1:
2966                         Colorize(ColorTell, FALSE);
2967                         curColor = ColorTell;
2968                         break;
2969                       case 2:
2970                         Colorize(ColorKibitz, FALSE);
2971                         curColor = ColorKibitz;
2972                         break;
2973                       case 3:
2974                         p = strrchr(star_match[1], '(');
2975                         if (p == NULL) {
2976                           p = star_match[1];
2977                         } else {
2978                           p++;
2979                         }
2980                         if (atoi(p) == 1) {
2981                           Colorize(ColorChannel1, FALSE);
2982                           curColor = ColorChannel1;
2983                         } else {
2984                           Colorize(ColorChannel, FALSE);
2985                           curColor = ColorChannel;
2986                         }
2987                         break;
2988                       case 5:
2989                         curColor = ColorNormal;
2990                         break;
2991                       }
2992                     }
2993                     if (started == STARTED_NONE && appData.autoComment &&
2994                         (gameMode == IcsObserving ||
2995                          gameMode == IcsPlayingWhite ||
2996                          gameMode == IcsPlayingBlack)) {
2997                       parse_pos = i - oldi;
2998                       memcpy(parse, &buf[oldi], parse_pos);
2999                       parse[parse_pos] = NULLCHAR;
3000                       started = STARTED_COMMENT;
3001                       savingComment = TRUE;
3002                     } else {
3003                       started = STARTED_CHATTER;
3004                       savingComment = FALSE;
3005                     }
3006                     loggedOn = TRUE;
3007                     continue;
3008                   }
3009                 }
3010
3011                 if (looking_at(buf, &i, "* s-shouts: ") ||
3012                     looking_at(buf, &i, "* c-shouts: ")) {
3013                     if (appData.colorize) {
3014                         if (oldi > next_out) {
3015                             SendToPlayer(&buf[next_out], oldi - next_out);
3016                             next_out = oldi;
3017                         }
3018                         Colorize(ColorSShout, FALSE);
3019                         curColor = ColorSShout;
3020                     }
3021                     loggedOn = TRUE;
3022                     started = STARTED_CHATTER;
3023                     continue;
3024                 }
3025
3026                 if (looking_at(buf, &i, "--->")) {
3027                     loggedOn = TRUE;
3028                     continue;
3029                 }
3030
3031                 if (looking_at(buf, &i, "* shouts: ") ||
3032                     looking_at(buf, &i, "--> ")) {
3033                     if (appData.colorize) {
3034                         if (oldi > next_out) {
3035                             SendToPlayer(&buf[next_out], oldi - next_out);
3036                             next_out = oldi;
3037                         }
3038                         Colorize(ColorShout, FALSE);
3039                         curColor = ColorShout;
3040                     }
3041                     loggedOn = TRUE;
3042                     started = STARTED_CHATTER;
3043                     continue;
3044                 }
3045
3046                 if (looking_at( buf, &i, "Challenge:")) {
3047                     if (appData.colorize) {
3048                         if (oldi > next_out) {
3049                             SendToPlayer(&buf[next_out], oldi - next_out);
3050                             next_out = oldi;
3051                         }
3052                         Colorize(ColorChallenge, FALSE);
3053                         curColor = ColorChallenge;
3054                     }
3055                     loggedOn = TRUE;
3056                     continue;
3057                 }
3058
3059                 if (looking_at(buf, &i, "* offers you") ||
3060                     looking_at(buf, &i, "* offers to be") ||
3061                     looking_at(buf, &i, "* would like to") ||
3062                     looking_at(buf, &i, "* requests to") ||
3063                     looking_at(buf, &i, "Your opponent offers") ||
3064                     looking_at(buf, &i, "Your opponent requests")) {
3065
3066                     if (appData.colorize) {
3067                         if (oldi > next_out) {
3068                             SendToPlayer(&buf[next_out], oldi - next_out);
3069                             next_out = oldi;
3070                         }
3071                         Colorize(ColorRequest, FALSE);
3072                         curColor = ColorRequest;
3073                     }
3074                     continue;
3075                 }
3076
3077                 if (looking_at(buf, &i, "* (*) seeking")) {
3078                     if (appData.colorize) {
3079                         if (oldi > next_out) {
3080                             SendToPlayer(&buf[next_out], oldi - next_out);
3081                             next_out = oldi;
3082                         }
3083                         Colorize(ColorSeek, FALSE);
3084                         curColor = ColorSeek;
3085                     }
3086                     continue;
3087             }
3088
3089             if (looking_at(buf, &i, "\\   ")) {
3090                 if (prevColor != ColorNormal) {
3091                     if (oldi > next_out) {
3092                         SendToPlayer(&buf[next_out], oldi - next_out);
3093                         next_out = oldi;
3094                     }
3095                     Colorize(prevColor, TRUE);
3096                     curColor = prevColor;
3097                 }
3098                 if (savingComment) {
3099                     parse_pos = i - oldi;
3100                     memcpy(parse, &buf[oldi], parse_pos);
3101                     parse[parse_pos] = NULLCHAR;
3102                     started = STARTED_COMMENT;
3103                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3104                         chattingPartner = savingComment - 3; // kludge to remember the box
3105                 } else {
3106                     started = STARTED_CHATTER;
3107                 }
3108                 continue;
3109             }
3110
3111             if (looking_at(buf, &i, "Black Strength :") ||
3112                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3113                 looking_at(buf, &i, "<10>") ||
3114                 looking_at(buf, &i, "#@#")) {
3115                 /* Wrong board style */
3116                 loggedOn = TRUE;
3117                 SendToICS(ics_prefix);
3118                 SendToICS("set style 12\n");
3119                 SendToICS(ics_prefix);
3120                 SendToICS("refresh\n");
3121                 continue;
3122             }
3123
3124             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3125                 ICSInitScript();
3126                 have_sent_ICS_logon = 1;
3127                 continue;
3128             }
3129
3130             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3131                 (looking_at(buf, &i, "\n<12> ") ||
3132                  looking_at(buf, &i, "<12> "))) {
3133                 loggedOn = TRUE;
3134                 if (oldi > next_out) {
3135                     SendToPlayer(&buf[next_out], oldi - next_out);
3136                 }
3137                 next_out = i;
3138                 started = STARTED_BOARD;
3139                 parse_pos = 0;
3140                 continue;
3141             }
3142
3143             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3144                 looking_at(buf, &i, "<b1> ")) {
3145                 if (oldi > next_out) {
3146                     SendToPlayer(&buf[next_out], oldi - next_out);
3147                 }
3148                 next_out = i;
3149                 started = STARTED_HOLDINGS;
3150                 parse_pos = 0;
3151                 continue;
3152             }
3153
3154             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3155                 loggedOn = TRUE;
3156                 /* Header for a move list -- first line */
3157
3158                 switch (ics_getting_history) {
3159                   case H_FALSE:
3160                     switch (gameMode) {
3161                       case IcsIdle:
3162                       case BeginningOfGame:
3163                         /* User typed "moves" or "oldmoves" while we
3164                            were idle.  Pretend we asked for these
3165                            moves and soak them up so user can step
3166                            through them and/or save them.
3167                            */
3168                         Reset(FALSE, TRUE);
3169                         gameMode = IcsObserving;
3170                         ModeHighlight();
3171                         ics_gamenum = -1;
3172                         ics_getting_history = H_GOT_UNREQ_HEADER;
3173                         break;
3174                       case EditGame: /*?*/
3175                       case EditPosition: /*?*/
3176                         /* Should above feature work in these modes too? */
3177                         /* For now it doesn't */
3178                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3179                         break;
3180                       default:
3181                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3182                         break;
3183                     }
3184                     break;
3185                   case H_REQUESTED:
3186                     /* Is this the right one? */
3187                     if (gameInfo.white && gameInfo.black &&
3188                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3189                         strcmp(gameInfo.black, star_match[2]) == 0) {
3190                         /* All is well */
3191                         ics_getting_history = H_GOT_REQ_HEADER;
3192                     }
3193                     break;
3194                   case H_GOT_REQ_HEADER:
3195                   case H_GOT_UNREQ_HEADER:
3196                   case H_GOT_UNWANTED_HEADER:
3197                   case H_GETTING_MOVES:
3198                     /* Should not happen */
3199                     DisplayError(_("Error gathering move list: two headers"), 0);
3200                     ics_getting_history = H_FALSE;
3201                     break;
3202                 }
3203
3204                 /* Save player ratings into gameInfo if needed */
3205                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3206                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3207                     (gameInfo.whiteRating == -1 ||
3208                      gameInfo.blackRating == -1)) {
3209
3210                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3211                     gameInfo.blackRating = string_to_rating(star_match[3]);
3212                     if (appData.debugMode)
3213                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3214                               gameInfo.whiteRating, gameInfo.blackRating);
3215                 }
3216                 continue;
3217             }
3218
3219             if (looking_at(buf, &i,
3220               "* * match, initial time: * minute*, increment: * second")) {
3221                 /* Header for a move list -- second line */
3222                 /* Initial board will follow if this is a wild game */
3223                 if (gameInfo.event != NULL) free(gameInfo.event);
3224                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3225                 gameInfo.event = StrSave(str);
3226                 /* [HGM] we switched variant. Translate boards if needed. */
3227                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3228                 continue;
3229             }
3230
3231             if (looking_at(buf, &i, "Move  ")) {
3232                 /* Beginning of a move list */
3233                 switch (ics_getting_history) {
3234                   case H_FALSE:
3235                     /* Normally should not happen */
3236                     /* Maybe user hit reset while we were parsing */
3237                     break;
3238                   case H_REQUESTED:
3239                     /* Happens if we are ignoring a move list that is not
3240                      * the one we just requested.  Common if the user
3241                      * tries to observe two games without turning off
3242                      * getMoveList */
3243                     break;
3244                   case H_GETTING_MOVES:
3245                     /* Should not happen */
3246                     DisplayError(_("Error gathering move list: nested"), 0);
3247                     ics_getting_history = H_FALSE;
3248                     break;
3249                   case H_GOT_REQ_HEADER:
3250                     ics_getting_history = H_GETTING_MOVES;
3251                     started = STARTED_MOVES;
3252                     parse_pos = 0;
3253                     if (oldi > next_out) {
3254                         SendToPlayer(&buf[next_out], oldi - next_out);
3255                     }
3256                     break;
3257                   case H_GOT_UNREQ_HEADER:
3258                     ics_getting_history = H_GETTING_MOVES;
3259                     started = STARTED_MOVES_NOHIDE;
3260                     parse_pos = 0;
3261                     break;
3262                   case H_GOT_UNWANTED_HEADER:
3263                     ics_getting_history = H_FALSE;
3264                     break;
3265                 }
3266                 continue;
3267             }
3268
3269             if (looking_at(buf, &i, "% ") ||
3270                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3271                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3272                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3273                     soughtPending = FALSE;
3274                     seekGraphUp = TRUE;
3275                     DrawSeekGraph();
3276                 }
3277                 if(suppressKibitz) next_out = i;
3278                 savingComment = FALSE;
3279                 suppressKibitz = 0;
3280                 switch (started) {
3281                   case STARTED_MOVES:
3282                   case STARTED_MOVES_NOHIDE:
3283                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3284                     parse[parse_pos + i - oldi] = NULLCHAR;
3285                     ParseGameHistory(parse);
3286 #if ZIPPY
3287                     if (appData.zippyPlay && first.initDone) {
3288                         FeedMovesToProgram(&first, forwardMostMove);
3289                         if (gameMode == IcsPlayingWhite) {
3290                             if (WhiteOnMove(forwardMostMove)) {
3291                                 if (first.sendTime) {
3292                                   if (first.useColors) {
3293                                     SendToProgram("black\n", &first);
3294                                   }
3295                                   SendTimeRemaining(&first, TRUE);
3296                                 }
3297                                 if (first.useColors) {
3298                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3299                                 }
3300                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3301                                 first.maybeThinking = TRUE;
3302                             } else {
3303                                 if (first.usePlayother) {
3304                                   if (first.sendTime) {
3305                                     SendTimeRemaining(&first, TRUE);
3306                                   }
3307                                   SendToProgram("playother\n", &first);
3308                                   firstMove = FALSE;
3309                                 } else {
3310                                   firstMove = TRUE;
3311                                 }
3312                             }
3313                         } else if (gameMode == IcsPlayingBlack) {
3314                             if (!WhiteOnMove(forwardMostMove)) {
3315                                 if (first.sendTime) {
3316                                   if (first.useColors) {
3317                                     SendToProgram("white\n", &first);
3318                                   }
3319                                   SendTimeRemaining(&first, FALSE);
3320                                 }
3321                                 if (first.useColors) {
3322                                   SendToProgram("black\n", &first);
3323                                 }
3324                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3325                                 first.maybeThinking = TRUE;
3326                             } else {
3327                                 if (first.usePlayother) {
3328                                   if (first.sendTime) {
3329                                     SendTimeRemaining(&first, FALSE);
3330                                   }
3331                                   SendToProgram("playother\n", &first);
3332                                   firstMove = FALSE;
3333                                 } else {
3334                                   firstMove = TRUE;
3335                                 }
3336                             }
3337                         }
3338                     }
3339 #endif
3340                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3341                         /* Moves came from oldmoves or moves command
3342                            while we weren't doing anything else.
3343                            */
3344                         currentMove = forwardMostMove;
3345                         ClearHighlights();/*!!could figure this out*/
3346                         flipView = appData.flipView;
3347                         DrawPosition(TRUE, boards[currentMove]);
3348                         DisplayBothClocks();
3349                         snprintf(str, MSG_SIZ, "%s vs. %s",
3350                                 gameInfo.white, gameInfo.black);
3351                         DisplayTitle(str);
3352                         gameMode = IcsIdle;
3353                     } else {
3354                         /* Moves were history of an active game */
3355                         if (gameInfo.resultDetails != NULL) {
3356                             free(gameInfo.resultDetails);
3357                             gameInfo.resultDetails = NULL;
3358                         }
3359                     }
3360                     HistorySet(parseList, backwardMostMove,
3361                                forwardMostMove, currentMove-1);
3362                     DisplayMove(currentMove - 1);
3363                     if (started == STARTED_MOVES) next_out = i;
3364                     started = STARTED_NONE;
3365                     ics_getting_history = H_FALSE;
3366                     break;
3367
3368                   case STARTED_OBSERVE:
3369                     started = STARTED_NONE;
3370                     SendToICS(ics_prefix);
3371                     SendToICS("refresh\n");
3372                     break;
3373
3374                   default:
3375                     break;
3376                 }
3377                 if(bookHit) { // [HGM] book: simulate book reply
3378                     static char bookMove[MSG_SIZ]; // a bit generous?
3379
3380                     programStats.nodes = programStats.depth = programStats.time =
3381                     programStats.score = programStats.got_only_move = 0;
3382                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3383
3384                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3385                     strcat(bookMove, bookHit);
3386                     HandleMachineMove(bookMove, &first);
3387                 }
3388                 continue;
3389             }
3390
3391             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3392                  started == STARTED_HOLDINGS ||
3393                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3394                 /* Accumulate characters in move list or board */
3395                 parse[parse_pos++] = buf[i];
3396             }
3397
3398             /* Start of game messages.  Mostly we detect start of game
3399                when the first board image arrives.  On some versions
3400                of the ICS, though, we need to do a "refresh" after starting
3401                to observe in order to get the current board right away. */
3402             if (looking_at(buf, &i, "Adding game * to observation list")) {
3403                 started = STARTED_OBSERVE;
3404                 continue;
3405             }
3406
3407             /* Handle auto-observe */
3408             if (appData.autoObserve &&
3409                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3410                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3411                 char *player;
3412                 /* Choose the player that was highlighted, if any. */
3413                 if (star_match[0][0] == '\033' ||
3414                     star_match[1][0] != '\033') {
3415                     player = star_match[0];
3416                 } else {
3417                     player = star_match[2];
3418                 }
3419                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3420                         ics_prefix, StripHighlightAndTitle(player));
3421                 SendToICS(str);
3422
3423                 /* Save ratings from notify string */
3424                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3425                 player1Rating = string_to_rating(star_match[1]);
3426                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3427                 player2Rating = string_to_rating(star_match[3]);
3428
3429                 if (appData.debugMode)
3430                   fprintf(debugFP,
3431                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3432                           player1Name, player1Rating,
3433                           player2Name, player2Rating);
3434
3435                 continue;
3436             }
3437
3438             /* Deal with automatic examine mode after a game,
3439                and with IcsObserving -> IcsExamining transition */
3440             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3441                 looking_at(buf, &i, "has made you an examiner of game *")) {
3442
3443                 int gamenum = atoi(star_match[0]);
3444                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3445                     gamenum == ics_gamenum) {
3446                     /* We were already playing or observing this game;
3447                        no need to refetch history */
3448                     gameMode = IcsExamining;
3449                     if (pausing) {
3450                         pauseExamForwardMostMove = forwardMostMove;
3451                     } else if (currentMove < forwardMostMove) {
3452                         ForwardInner(forwardMostMove);
3453                     }
3454                 } else {
3455                     /* I don't think this case really can happen */
3456                     SendToICS(ics_prefix);
3457                     SendToICS("refresh\n");
3458                 }
3459                 continue;
3460             }
3461
3462             /* Error messages */
3463 //          if (ics_user_moved) {
3464             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3465                 if (looking_at(buf, &i, "Illegal move") ||
3466                     looking_at(buf, &i, "Not a legal move") ||
3467                     looking_at(buf, &i, "Your king is in check") ||
3468                     looking_at(buf, &i, "It isn't your turn") ||
3469                     looking_at(buf, &i, "It is not your move")) {
3470                     /* Illegal move */
3471                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3472                         currentMove = forwardMostMove-1;
3473                         DisplayMove(currentMove - 1); /* before DMError */
3474                         DrawPosition(FALSE, boards[currentMove]);
3475                         SwitchClocks(forwardMostMove-1); // [HGM] race
3476                         DisplayBothClocks();
3477                     }
3478                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3479                     ics_user_moved = 0;
3480                     continue;
3481                 }
3482             }
3483
3484             if (looking_at(buf, &i, "still have time") ||
3485                 looking_at(buf, &i, "not out of time") ||
3486                 looking_at(buf, &i, "either player is out of time") ||
3487                 looking_at(buf, &i, "has timeseal; checking")) {
3488                 /* We must have called his flag a little too soon */
3489                 whiteFlag = blackFlag = FALSE;
3490                 continue;
3491             }
3492
3493             if (looking_at(buf, &i, "added * seconds to") ||
3494                 looking_at(buf, &i, "seconds were added to")) {
3495                 /* Update the clocks */
3496                 SendToICS(ics_prefix);
3497                 SendToICS("refresh\n");
3498                 continue;
3499             }
3500
3501             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3502                 ics_clock_paused = TRUE;
3503                 StopClocks();
3504                 continue;
3505             }
3506
3507             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3508                 ics_clock_paused = FALSE;
3509                 StartClocks();
3510                 continue;
3511             }
3512
3513             /* Grab player ratings from the Creating: message.
3514                Note we have to check for the special case when
3515                the ICS inserts things like [white] or [black]. */
3516             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3517                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3518                 /* star_matches:
3519                    0    player 1 name (not necessarily white)
3520                    1    player 1 rating
3521                    2    empty, white, or black (IGNORED)
3522                    3    player 2 name (not necessarily black)
3523                    4    player 2 rating
3524
3525                    The names/ratings are sorted out when the game
3526                    actually starts (below).
3527                 */
3528                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3529                 player1Rating = string_to_rating(star_match[1]);
3530                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3531                 player2Rating = string_to_rating(star_match[4]);
3532
3533                 if (appData.debugMode)
3534                   fprintf(debugFP,
3535                           "Ratings from 'Creating:' %s %d, %s %d\n",
3536                           player1Name, player1Rating,
3537                           player2Name, player2Rating);
3538
3539                 continue;
3540             }
3541
3542             /* Improved generic start/end-of-game messages */
3543             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3544                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3545                 /* If tkind == 0: */
3546                 /* star_match[0] is the game number */
3547                 /*           [1] is the white player's name */
3548                 /*           [2] is the black player's name */
3549                 /* For end-of-game: */
3550                 /*           [3] is the reason for the game end */
3551                 /*           [4] is a PGN end game-token, preceded by " " */
3552                 /* For start-of-game: */
3553                 /*           [3] begins with "Creating" or "Continuing" */
3554                 /*           [4] is " *" or empty (don't care). */
3555                 int gamenum = atoi(star_match[0]);
3556                 char *whitename, *blackname, *why, *endtoken;
3557                 ChessMove endtype = EndOfFile;
3558
3559                 if (tkind == 0) {
3560                   whitename = star_match[1];
3561                   blackname = star_match[2];
3562                   why = star_match[3];
3563                   endtoken = star_match[4];
3564                 } else {
3565                   whitename = star_match[1];
3566                   blackname = star_match[3];
3567                   why = star_match[5];
3568                   endtoken = star_match[6];
3569                 }
3570
3571                 /* Game start messages */
3572                 if (strncmp(why, "Creating ", 9) == 0 ||
3573                     strncmp(why, "Continuing ", 11) == 0) {
3574                     gs_gamenum = gamenum;
3575                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3576                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3577 #if ZIPPY
3578                     if (appData.zippyPlay) {
3579                         ZippyGameStart(whitename, blackname);
3580                     }
3581 #endif /*ZIPPY*/
3582                     partnerBoardValid = FALSE; // [HGM] bughouse
3583                     continue;
3584                 }
3585
3586                 /* Game end messages */
3587                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3588                     ics_gamenum != gamenum) {
3589                     continue;
3590                 }
3591                 while (endtoken[0] == ' ') endtoken++;
3592                 switch (endtoken[0]) {
3593                   case '*':
3594                   default:
3595                     endtype = GameUnfinished;
3596                     break;
3597                   case '0':
3598                     endtype = BlackWins;
3599                     break;
3600                   case '1':
3601                     if (endtoken[1] == '/')
3602                       endtype = GameIsDrawn;
3603                     else
3604                       endtype = WhiteWins;
3605                     break;
3606                 }
3607                 GameEnds(endtype, why, GE_ICS);
3608 #if ZIPPY
3609                 if (appData.zippyPlay && first.initDone) {
3610                     ZippyGameEnd(endtype, why);
3611                     if (first.pr == NULL) {
3612                       /* Start the next process early so that we'll
3613                          be ready for the next challenge */
3614                       StartChessProgram(&first);
3615                     }
3616                     /* Send "new" early, in case this command takes
3617                        a long time to finish, so that we'll be ready
3618                        for the next challenge. */
3619                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3620                     Reset(TRUE, TRUE);
3621                 }
3622 #endif /*ZIPPY*/
3623                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3624                 continue;
3625             }
3626
3627             if (looking_at(buf, &i, "Removing game * from observation") ||
3628                 looking_at(buf, &i, "no longer observing game *") ||
3629                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3630                 if (gameMode == IcsObserving &&
3631                     atoi(star_match[0]) == ics_gamenum)
3632                   {
3633                       /* icsEngineAnalyze */
3634                       if (appData.icsEngineAnalyze) {
3635                             ExitAnalyzeMode();
3636                             ModeHighlight();
3637                       }
3638                       StopClocks();
3639                       gameMode = IcsIdle;
3640                       ics_gamenum = -1;
3641                       ics_user_moved = FALSE;
3642                   }
3643                 continue;
3644             }
3645
3646             if (looking_at(buf, &i, "no longer examining game *")) {
3647                 if (gameMode == IcsExamining &&
3648                     atoi(star_match[0]) == ics_gamenum)
3649                   {
3650                       gameMode = IcsIdle;
3651                       ics_gamenum = -1;
3652                       ics_user_moved = FALSE;
3653                   }
3654                 continue;
3655             }
3656
3657             /* Advance leftover_start past any newlines we find,
3658                so only partial lines can get reparsed */
3659             if (looking_at(buf, &i, "\n")) {
3660                 prevColor = curColor;
3661                 if (curColor != ColorNormal) {
3662                     if (oldi > next_out) {
3663                         SendToPlayer(&buf[next_out], oldi - next_out);
3664                         next_out = oldi;
3665                     }
3666                     Colorize(ColorNormal, FALSE);
3667                     curColor = ColorNormal;
3668                 }
3669                 if (started == STARTED_BOARD) {
3670                     started = STARTED_NONE;
3671                     parse[parse_pos] = NULLCHAR;
3672                     ParseBoard12(parse);
3673                     ics_user_moved = 0;
3674
3675                     /* Send premove here */
3676                     if (appData.premove) {
3677                       char str[MSG_SIZ];
3678                       if (currentMove == 0 &&
3679                           gameMode == IcsPlayingWhite &&
3680                           appData.premoveWhite) {
3681                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3682                         if (appData.debugMode)
3683                           fprintf(debugFP, "Sending premove:\n");
3684                         SendToICS(str);
3685                       } else if (currentMove == 1 &&
3686                                  gameMode == IcsPlayingBlack &&
3687                                  appData.premoveBlack) {
3688                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3689                         if (appData.debugMode)
3690                           fprintf(debugFP, "Sending premove:\n");
3691                         SendToICS(str);
3692                       } else if (gotPremove) {
3693                         gotPremove = 0;
3694                         ClearPremoveHighlights();
3695                         if (appData.debugMode)
3696                           fprintf(debugFP, "Sending premove:\n");
3697                           UserMoveEvent(premoveFromX, premoveFromY,
3698                                         premoveToX, premoveToY,
3699                                         premovePromoChar);
3700                       }
3701                     }
3702
3703                     /* Usually suppress following prompt */
3704                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3705                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3706                         if (looking_at(buf, &i, "*% ")) {
3707                             savingComment = FALSE;
3708                             suppressKibitz = 0;
3709                         }
3710                     }
3711                     next_out = i;
3712                 } else if (started == STARTED_HOLDINGS) {
3713                     int gamenum;
3714                     char new_piece[MSG_SIZ];
3715                     started = STARTED_NONE;
3716                     parse[parse_pos] = NULLCHAR;
3717                     if (appData.debugMode)
3718                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3719                                                         parse, currentMove);
3720                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3721                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3722                         if (gameInfo.variant == VariantNormal) {
3723                           /* [HGM] We seem to switch variant during a game!
3724                            * Presumably no holdings were displayed, so we have
3725                            * to move the position two files to the right to
3726                            * create room for them!
3727                            */
3728                           VariantClass newVariant;
3729                           switch(gameInfo.boardWidth) { // base guess on board width
3730                                 case 9:  newVariant = VariantShogi; break;
3731                                 case 10: newVariant = VariantGreat; break;
3732                                 default: newVariant = VariantCrazyhouse; break;
3733                           }
3734                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3735                           /* Get a move list just to see the header, which
3736                              will tell us whether this is really bug or zh */
3737                           if (ics_getting_history == H_FALSE) {
3738                             ics_getting_history = H_REQUESTED;
3739                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3740                             SendToICS(str);
3741                           }
3742                         }
3743                         new_piece[0] = NULLCHAR;
3744                         sscanf(parse, "game %d white [%s black [%s <- %s",
3745                                &gamenum, white_holding, black_holding,
3746                                new_piece);
3747                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3748                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3749                         /* [HGM] copy holdings to board holdings area */
3750                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3751                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3752                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3753 #if ZIPPY
3754                         if (appData.zippyPlay && first.initDone) {
3755                             ZippyHoldings(white_holding, black_holding,
3756                                           new_piece);
3757                         }
3758 #endif /*ZIPPY*/
3759                         if (tinyLayout || smallLayout) {
3760                             char wh[16], bh[16];
3761                             PackHolding(wh, white_holding);
3762                             PackHolding(bh, black_holding);
3763                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3764                                     gameInfo.white, gameInfo.black);
3765                         } else {
3766                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3767                                     gameInfo.white, white_holding,
3768                                     gameInfo.black, black_holding);
3769                         }
3770                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3771                         DrawPosition(FALSE, boards[currentMove]);
3772                         DisplayTitle(str);
3773                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3774                         sscanf(parse, "game %d white [%s black [%s <- %s",
3775                                &gamenum, white_holding, black_holding,
3776                                new_piece);
3777                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3778                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3779                         /* [HGM] copy holdings to partner-board holdings area */
3780                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3781                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3782                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3783                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3784                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3785                       }
3786                     }
3787                     /* Suppress following prompt */
3788                     if (looking_at(buf, &i, "*% ")) {
3789                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3790                         savingComment = FALSE;
3791                         suppressKibitz = 0;
3792                     }
3793                     next_out = i;
3794                 }
3795                 continue;
3796             }
3797
3798             i++;                /* skip unparsed character and loop back */
3799         }
3800
3801         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3802 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3803 //          SendToPlayer(&buf[next_out], i - next_out);
3804             started != STARTED_HOLDINGS && leftover_start > next_out) {
3805             SendToPlayer(&buf[next_out], leftover_start - next_out);
3806             next_out = i;
3807         }
3808
3809         leftover_len = buf_len - leftover_start;
3810         /* if buffer ends with something we couldn't parse,
3811            reparse it after appending the next read */
3812
3813     } else if (count == 0) {
3814         RemoveInputSource(isr);
3815         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3816     } else {
3817         DisplayFatalError(_("Error reading from ICS"), error, 1);
3818     }
3819 }
3820
3821
3822 /* Board style 12 looks like this:
3823
3824    <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
3825
3826  * The "<12> " is stripped before it gets to this routine.  The two
3827  * trailing 0's (flip state and clock ticking) are later addition, and
3828  * some chess servers may not have them, or may have only the first.
3829  * Additional trailing fields may be added in the future.
3830  */
3831
3832 #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"
3833
3834 #define RELATION_OBSERVING_PLAYED    0
3835 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3836 #define RELATION_PLAYING_MYMOVE      1
3837 #define RELATION_PLAYING_NOTMYMOVE  -1
3838 #define RELATION_EXAMINING           2
3839 #define RELATION_ISOLATED_BOARD     -3
3840 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3841
3842 void
3843 ParseBoard12(string)
3844      char *string;
3845 {
3846     GameMode newGameMode;
3847     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3848     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3849     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3850     char to_play, board_chars[200];
3851     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3852     char black[32], white[32];
3853     Board board;
3854     int prevMove = currentMove;
3855     int ticking = 2;
3856     ChessMove moveType;
3857     int fromX, fromY, toX, toY;
3858     char promoChar;
3859     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3860     char *bookHit = NULL; // [HGM] book
3861     Boolean weird = FALSE, reqFlag = FALSE;
3862
3863     fromX = fromY = toX = toY = -1;
3864
3865     newGame = FALSE;
3866
3867     if (appData.debugMode)
3868       fprintf(debugFP, _("Parsing board: %s\n"), string);
3869
3870     move_str[0] = NULLCHAR;
3871     elapsed_time[0] = NULLCHAR;
3872     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3873         int  i = 0, j;
3874         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3875             if(string[i] == ' ') { ranks++; files = 0; }
3876             else files++;
3877             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3878             i++;
3879         }
3880         for(j = 0; j <i; j++) board_chars[j] = string[j];
3881         board_chars[i] = '\0';
3882         string += i + 1;
3883     }
3884     n = sscanf(string, PATTERN, &to_play, &double_push,
3885                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3886                &gamenum, white, black, &relation, &basetime, &increment,
3887                &white_stren, &black_stren, &white_time, &black_time,
3888                &moveNum, str, elapsed_time, move_str, &ics_flip,
3889                &ticking);
3890
3891     if (n < 21) {
3892         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3893         DisplayError(str, 0);
3894         return;
3895     }
3896
3897     /* Convert the move number to internal form */
3898     moveNum = (moveNum - 1) * 2;
3899     if (to_play == 'B') moveNum++;
3900     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3901       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3902                         0, 1);
3903       return;
3904     }
3905
3906     switch (relation) {
3907       case RELATION_OBSERVING_PLAYED:
3908       case RELATION_OBSERVING_STATIC:
3909         if (gamenum == -1) {
3910             /* Old ICC buglet */
3911             relation = RELATION_OBSERVING_STATIC;
3912         }
3913         newGameMode = IcsObserving;
3914         break;
3915       case RELATION_PLAYING_MYMOVE:
3916       case RELATION_PLAYING_NOTMYMOVE:
3917         newGameMode =
3918           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3919             IcsPlayingWhite : IcsPlayingBlack;
3920         break;
3921       case RELATION_EXAMINING:
3922         newGameMode = IcsExamining;
3923         break;
3924       case RELATION_ISOLATED_BOARD:
3925       default:
3926         /* Just display this board.  If user was doing something else,
3927            we will forget about it until the next board comes. */
3928         newGameMode = IcsIdle;
3929         break;
3930       case RELATION_STARTING_POSITION:
3931         newGameMode = gameMode;
3932         break;
3933     }
3934
3935     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3936          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3937       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3938       char *toSqr;
3939       for (k = 0; k < ranks; k++) {
3940         for (j = 0; j < files; j++)
3941           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3942         if(gameInfo.holdingsWidth > 1) {
3943              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3944              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3945         }
3946       }
3947       CopyBoard(partnerBoard, board);
3948       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3949         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3950         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3951       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3952       if(toSqr = strchr(str, '-')) {
3953         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3954         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3955       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3956       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3957       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3958       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3959       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3960       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3961                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3962       DisplayMessage(partnerStatus, "");
3963         partnerBoardValid = TRUE;
3964       return;
3965     }
3966
3967     /* Modify behavior for initial board display on move listing
3968        of wild games.
3969        */
3970     switch (ics_getting_history) {
3971       case H_FALSE:
3972       case H_REQUESTED:
3973         break;
3974       case H_GOT_REQ_HEADER:
3975       case H_GOT_UNREQ_HEADER:
3976         /* This is the initial position of the current game */
3977         gamenum = ics_gamenum;
3978         moveNum = 0;            /* old ICS bug workaround */
3979         if (to_play == 'B') {
3980           startedFromSetupPosition = TRUE;
3981           blackPlaysFirst = TRUE;
3982           moveNum = 1;
3983           if (forwardMostMove == 0) forwardMostMove = 1;
3984           if (backwardMostMove == 0) backwardMostMove = 1;
3985           if (currentMove == 0) currentMove = 1;
3986         }
3987         newGameMode = gameMode;
3988         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3989         break;
3990       case H_GOT_UNWANTED_HEADER:
3991         /* This is an initial board that we don't want */
3992         return;
3993       case H_GETTING_MOVES:
3994         /* Should not happen */
3995         DisplayError(_("Error gathering move list: extra board"), 0);
3996         ics_getting_history = H_FALSE;
3997         return;
3998     }
3999
4000    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4001                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4002      /* [HGM] We seem to have switched variant unexpectedly
4003       * Try to guess new variant from board size
4004       */
4005           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4006           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4007           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4008           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4009           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4010           if(!weird) newVariant = VariantNormal;
4011           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4012           /* Get a move list just to see the header, which
4013              will tell us whether this is really bug or zh */
4014           if (ics_getting_history == H_FALSE) {
4015             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4016             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4017             SendToICS(str);
4018           }
4019     }
4020
4021     /* Take action if this is the first board of a new game, or of a
4022        different game than is currently being displayed.  */
4023     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4024         relation == RELATION_ISOLATED_BOARD) {
4025
4026         /* Forget the old game and get the history (if any) of the new one */
4027         if (gameMode != BeginningOfGame) {
4028           Reset(TRUE, TRUE);
4029         }
4030         newGame = TRUE;
4031         if (appData.autoRaiseBoard) BoardToTop();
4032         prevMove = -3;
4033         if (gamenum == -1) {
4034             newGameMode = IcsIdle;
4035         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4036                    appData.getMoveList && !reqFlag) {
4037             /* Need to get game history */
4038             ics_getting_history = H_REQUESTED;
4039             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4040             SendToICS(str);
4041         }
4042
4043         /* Initially flip the board to have black on the bottom if playing
4044            black or if the ICS flip flag is set, but let the user change
4045            it with the Flip View button. */
4046         flipView = appData.autoFlipView ?
4047           (newGameMode == IcsPlayingBlack) || ics_flip :
4048           appData.flipView;
4049
4050         /* Done with values from previous mode; copy in new ones */
4051         gameMode = newGameMode;
4052         ModeHighlight();
4053         ics_gamenum = gamenum;
4054         if (gamenum == gs_gamenum) {
4055             int klen = strlen(gs_kind);
4056             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4057             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4058             gameInfo.event = StrSave(str);
4059         } else {
4060             gameInfo.event = StrSave("ICS game");
4061         }
4062         gameInfo.site = StrSave(appData.icsHost);
4063         gameInfo.date = PGNDate();
4064         gameInfo.round = StrSave("-");
4065         gameInfo.white = StrSave(white);
4066         gameInfo.black = StrSave(black);
4067         timeControl = basetime * 60 * 1000;
4068         timeControl_2 = 0;
4069         timeIncrement = increment * 1000;
4070         movesPerSession = 0;
4071         gameInfo.timeControl = TimeControlTagValue();
4072         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4073   if (appData.debugMode) {
4074     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4075     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4076     setbuf(debugFP, NULL);
4077   }
4078
4079         gameInfo.outOfBook = NULL;
4080
4081         /* Do we have the ratings? */
4082         if (strcmp(player1Name, white) == 0 &&
4083             strcmp(player2Name, black) == 0) {
4084             if (appData.debugMode)
4085               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4086                       player1Rating, player2Rating);
4087             gameInfo.whiteRating = player1Rating;
4088             gameInfo.blackRating = player2Rating;
4089         } else if (strcmp(player2Name, white) == 0 &&
4090                    strcmp(player1Name, black) == 0) {
4091             if (appData.debugMode)
4092               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4093                       player2Rating, player1Rating);
4094             gameInfo.whiteRating = player2Rating;
4095             gameInfo.blackRating = player1Rating;
4096         }
4097         player1Name[0] = player2Name[0] = NULLCHAR;
4098
4099         /* Silence shouts if requested */
4100         if (appData.quietPlay &&
4101             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4102             SendToICS(ics_prefix);
4103             SendToICS("set shout 0\n");
4104         }
4105     }
4106
4107     /* Deal with midgame name changes */
4108     if (!newGame) {
4109         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4110             if (gameInfo.white) free(gameInfo.white);
4111             gameInfo.white = StrSave(white);
4112         }
4113         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4114             if (gameInfo.black) free(gameInfo.black);
4115             gameInfo.black = StrSave(black);
4116         }
4117     }
4118
4119     /* Throw away game result if anything actually changes in examine mode */
4120     if (gameMode == IcsExamining && !newGame) {
4121         gameInfo.result = GameUnfinished;
4122         if (gameInfo.resultDetails != NULL) {
4123             free(gameInfo.resultDetails);
4124             gameInfo.resultDetails = NULL;
4125         }
4126     }
4127
4128     /* In pausing && IcsExamining mode, we ignore boards coming
4129        in if they are in a different variation than we are. */
4130     if (pauseExamInvalid) return;
4131     if (pausing && gameMode == IcsExamining) {
4132         if (moveNum <= pauseExamForwardMostMove) {
4133             pauseExamInvalid = TRUE;
4134             forwardMostMove = pauseExamForwardMostMove;
4135             return;
4136         }
4137     }
4138
4139   if (appData.debugMode) {
4140     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4141   }
4142     /* Parse the board */
4143     for (k = 0; k < ranks; k++) {
4144       for (j = 0; j < files; j++)
4145         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4146       if(gameInfo.holdingsWidth > 1) {
4147            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4148            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4149       }
4150     }
4151     CopyBoard(boards[moveNum], board);
4152     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4153     if (moveNum == 0) {
4154         startedFromSetupPosition =
4155           !CompareBoards(board, initialPosition);
4156         if(startedFromSetupPosition)
4157             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4158     }
4159
4160     /* [HGM] Set castling rights. Take the outermost Rooks,
4161        to make it also work for FRC opening positions. Note that board12
4162        is really defective for later FRC positions, as it has no way to
4163        indicate which Rook can castle if they are on the same side of King.
4164        For the initial position we grant rights to the outermost Rooks,
4165        and remember thos rights, and we then copy them on positions
4166        later in an FRC game. This means WB might not recognize castlings with
4167        Rooks that have moved back to their original position as illegal,
4168        but in ICS mode that is not its job anyway.
4169     */
4170     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4171     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4172
4173         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4174             if(board[0][i] == WhiteRook) j = i;
4175         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4176         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4177             if(board[0][i] == WhiteRook) j = i;
4178         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4179         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4180             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4181         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4182         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4183             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4184         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4185
4186         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4187         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4188             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4189         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4190             if(board[BOARD_HEIGHT-1][k] == bKing)
4191                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4192         if(gameInfo.variant == VariantTwoKings) {
4193             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4194             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4195             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4196         }
4197     } else { int r;
4198         r = boards[moveNum][CASTLING][0] = initialRights[0];
4199         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4200         r = boards[moveNum][CASTLING][1] = initialRights[1];
4201         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4202         r = boards[moveNum][CASTLING][3] = initialRights[3];
4203         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4204         r = boards[moveNum][CASTLING][4] = initialRights[4];
4205         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4206         /* wildcastle kludge: always assume King has rights */
4207         r = boards[moveNum][CASTLING][2] = initialRights[2];
4208         r = boards[moveNum][CASTLING][5] = initialRights[5];
4209     }
4210     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4211     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4212
4213
4214     if (ics_getting_history == H_GOT_REQ_HEADER ||
4215         ics_getting_history == H_GOT_UNREQ_HEADER) {
4216         /* This was an initial position from a move list, not
4217            the current position */
4218         return;
4219     }
4220
4221     /* Update currentMove and known move number limits */
4222     newMove = newGame || moveNum > forwardMostMove;
4223
4224     if (newGame) {
4225         forwardMostMove = backwardMostMove = currentMove = moveNum;
4226         if (gameMode == IcsExamining && moveNum == 0) {
4227           /* Workaround for ICS limitation: we are not told the wild
4228              type when starting to examine a game.  But if we ask for
4229              the move list, the move list header will tell us */
4230             ics_getting_history = H_REQUESTED;
4231             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4232             SendToICS(str);
4233         }
4234     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4235                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4236 #if ZIPPY
4237         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4238         /* [HGM] applied this also to an engine that is silently watching        */
4239         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4240             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4241             gameInfo.variant == currentlyInitializedVariant) {
4242           takeback = forwardMostMove - moveNum;
4243           for (i = 0; i < takeback; i++) {
4244             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4245             SendToProgram("undo\n", &first);
4246           }
4247         }
4248 #endif
4249
4250         forwardMostMove = moveNum;
4251         if (!pausing || currentMove > forwardMostMove)
4252           currentMove = forwardMostMove;
4253     } else {
4254         /* New part of history that is not contiguous with old part */
4255         if (pausing && gameMode == IcsExamining) {
4256             pauseExamInvalid = TRUE;
4257             forwardMostMove = pauseExamForwardMostMove;
4258             return;
4259         }
4260         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4261 #if ZIPPY
4262             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4263                 // [HGM] when we will receive the move list we now request, it will be
4264                 // fed to the engine from the first move on. So if the engine is not
4265                 // in the initial position now, bring it there.
4266                 InitChessProgram(&first, 0);
4267             }
4268 #endif
4269             ics_getting_history = H_REQUESTED;
4270             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4271             SendToICS(str);
4272         }
4273         forwardMostMove = backwardMostMove = currentMove = moveNum;
4274     }
4275
4276     /* Update the clocks */
4277     if (strchr(elapsed_time, '.')) {
4278       /* Time is in ms */
4279       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4280       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4281     } else {
4282       /* Time is in seconds */
4283       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4284       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4285     }
4286
4287
4288 #if ZIPPY
4289     if (appData.zippyPlay && newGame &&
4290         gameMode != IcsObserving && gameMode != IcsIdle &&
4291         gameMode != IcsExamining)
4292       ZippyFirstBoard(moveNum, basetime, increment);
4293 #endif
4294
4295     /* Put the move on the move list, first converting
4296        to canonical algebraic form. */
4297     if (moveNum > 0) {
4298   if (appData.debugMode) {
4299     if (appData.debugMode) { int f = forwardMostMove;
4300         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4301                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4302                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4303     }
4304     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4305     fprintf(debugFP, "moveNum = %d\n", moveNum);
4306     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4307     setbuf(debugFP, NULL);
4308   }
4309         if (moveNum <= backwardMostMove) {
4310             /* We don't know what the board looked like before
4311                this move.  Punt. */
4312           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4313             strcat(parseList[moveNum - 1], " ");
4314             strcat(parseList[moveNum - 1], elapsed_time);
4315             moveList[moveNum - 1][0] = NULLCHAR;
4316         } else if (strcmp(move_str, "none") == 0) {
4317             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4318             /* Again, we don't know what the board looked like;
4319                this is really the start of the game. */
4320             parseList[moveNum - 1][0] = NULLCHAR;
4321             moveList[moveNum - 1][0] = NULLCHAR;
4322             backwardMostMove = moveNum;
4323             startedFromSetupPosition = TRUE;
4324             fromX = fromY = toX = toY = -1;
4325         } else {
4326           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4327           //                 So we parse the long-algebraic move string in stead of the SAN move
4328           int valid; char buf[MSG_SIZ], *prom;
4329
4330           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4331                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4332           // str looks something like "Q/a1-a2"; kill the slash
4333           if(str[1] == '/')
4334             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4335           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4336           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4337                 strcat(buf, prom); // long move lacks promo specification!
4338           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4339                 if(appData.debugMode)
4340                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4341                 safeStrCpy(move_str, buf, MSG_SIZ);
4342           }
4343           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4344                                 &fromX, &fromY, &toX, &toY, &promoChar)
4345                || ParseOneMove(buf, moveNum - 1, &moveType,
4346                                 &fromX, &fromY, &toX, &toY, &promoChar);
4347           // end of long SAN patch
4348           if (valid) {
4349             (void) CoordsToAlgebraic(boards[moveNum - 1],
4350                                      PosFlags(moveNum - 1),
4351                                      fromY, fromX, toY, toX, promoChar,
4352                                      parseList[moveNum-1]);
4353             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4354               case MT_NONE:
4355               case MT_STALEMATE:
4356               default:
4357                 break;
4358               case MT_CHECK:
4359                 if(gameInfo.variant != VariantShogi)
4360                     strcat(parseList[moveNum - 1], "+");
4361                 break;
4362               case MT_CHECKMATE:
4363               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4364                 strcat(parseList[moveNum - 1], "#");
4365                 break;
4366             }
4367             strcat(parseList[moveNum - 1], " ");
4368             strcat(parseList[moveNum - 1], elapsed_time);
4369             /* currentMoveString is set as a side-effect of ParseOneMove */
4370             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4371             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4372             strcat(moveList[moveNum - 1], "\n");
4373
4374             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4375                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4376               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4377                 ChessSquare old, new = boards[moveNum][k][j];
4378                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4379                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4380                   if(old == new) continue;
4381                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4382                   else if(new == WhiteWazir || new == BlackWazir) {
4383                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4384                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4385                       else boards[moveNum][k][j] = old; // preserve type of Gold
4386                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4387                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4388               }
4389           } else {
4390             /* Move from ICS was illegal!?  Punt. */
4391             if (appData.debugMode) {
4392               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4393               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4394             }
4395             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4396             strcat(parseList[moveNum - 1], " ");
4397             strcat(parseList[moveNum - 1], elapsed_time);
4398             moveList[moveNum - 1][0] = NULLCHAR;
4399             fromX = fromY = toX = toY = -1;
4400           }
4401         }
4402   if (appData.debugMode) {
4403     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4404     setbuf(debugFP, NULL);
4405   }
4406
4407 #if ZIPPY
4408         /* Send move to chess program (BEFORE animating it). */
4409         if (appData.zippyPlay && !newGame && newMove &&
4410            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4411
4412             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4413                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4414                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4415                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4416                             move_str);
4417                     DisplayError(str, 0);
4418                 } else {
4419                     if (first.sendTime) {
4420                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4421                     }
4422                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4423                     if (firstMove && !bookHit) {
4424                         firstMove = FALSE;
4425                         if (first.useColors) {
4426                           SendToProgram(gameMode == IcsPlayingWhite ?
4427                                         "white\ngo\n" :
4428                                         "black\ngo\n", &first);
4429                         } else {
4430                           SendToProgram("go\n", &first);
4431                         }
4432                         first.maybeThinking = TRUE;
4433                     }
4434                 }
4435             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4436               if (moveList[moveNum - 1][0] == NULLCHAR) {
4437                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4438                 DisplayError(str, 0);
4439               } else {
4440                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4441                 SendMoveToProgram(moveNum - 1, &first);
4442               }
4443             }
4444         }
4445 #endif
4446     }
4447
4448     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4449         /* If move comes from a remote source, animate it.  If it
4450            isn't remote, it will have already been animated. */
4451         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4452             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4453         }
4454         if (!pausing && appData.highlightLastMove) {
4455             SetHighlights(fromX, fromY, toX, toY);
4456         }
4457     }
4458
4459     /* Start the clocks */
4460     whiteFlag = blackFlag = FALSE;
4461     appData.clockMode = !(basetime == 0 && increment == 0);
4462     if (ticking == 0) {
4463       ics_clock_paused = TRUE;
4464       StopClocks();
4465     } else if (ticking == 1) {
4466       ics_clock_paused = FALSE;
4467     }
4468     if (gameMode == IcsIdle ||
4469         relation == RELATION_OBSERVING_STATIC ||
4470         relation == RELATION_EXAMINING ||
4471         ics_clock_paused)
4472       DisplayBothClocks();
4473     else
4474       StartClocks();
4475
4476     /* Display opponents and material strengths */
4477     if (gameInfo.variant != VariantBughouse &&
4478         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4479         if (tinyLayout || smallLayout) {
4480             if(gameInfo.variant == VariantNormal)
4481               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4482                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4483                     basetime, increment);
4484             else
4485               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4486                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4487                     basetime, increment, (int) gameInfo.variant);
4488         } else {
4489             if(gameInfo.variant == VariantNormal)
4490               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4491                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4492                     basetime, increment);
4493             else
4494               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4495                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4496                     basetime, increment, VariantName(gameInfo.variant));
4497         }
4498         DisplayTitle(str);
4499   if (appData.debugMode) {
4500     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4501   }
4502     }
4503
4504
4505     /* Display the board */
4506     if (!pausing && !appData.noGUI) {
4507
4508       if (appData.premove)
4509           if (!gotPremove ||
4510              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4511              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4512               ClearPremoveHighlights();
4513
4514       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4515         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4516       DrawPosition(j, boards[currentMove]);
4517
4518       DisplayMove(moveNum - 1);
4519       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4520             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4521               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4522         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4523       }
4524     }
4525
4526     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4527 #if ZIPPY
4528     if(bookHit) { // [HGM] book: simulate book reply
4529         static char bookMove[MSG_SIZ]; // a bit generous?
4530
4531         programStats.nodes = programStats.depth = programStats.time =
4532         programStats.score = programStats.got_only_move = 0;
4533         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4534
4535         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4536         strcat(bookMove, bookHit);
4537         HandleMachineMove(bookMove, &first);
4538     }
4539 #endif
4540 }
4541
4542 void
4543 GetMoveListEvent()
4544 {
4545     char buf[MSG_SIZ];
4546     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4547         ics_getting_history = H_REQUESTED;
4548         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4549         SendToICS(buf);
4550     }
4551 }
4552
4553 void
4554 AnalysisPeriodicEvent(force)
4555      int force;
4556 {
4557     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4558          && !force) || !appData.periodicUpdates)
4559       return;
4560
4561     /* Send . command to Crafty to collect stats */
4562     SendToProgram(".\n", &first);
4563
4564     /* Don't send another until we get a response (this makes
4565        us stop sending to old Crafty's which don't understand
4566        the "." command (sending illegal cmds resets node count & time,
4567        which looks bad)) */
4568     programStats.ok_to_send = 0;
4569 }
4570
4571 void ics_update_width(new_width)
4572         int new_width;
4573 {
4574         ics_printf("set width %d\n", new_width);
4575 }
4576
4577 void
4578 SendMoveToProgram(moveNum, cps)
4579      int moveNum;
4580      ChessProgramState *cps;
4581 {
4582     char buf[MSG_SIZ];
4583
4584     if (cps->useUsermove) {
4585       SendToProgram("usermove ", cps);
4586     }
4587     if (cps->useSAN) {
4588       char *space;
4589       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4590         int len = space - parseList[moveNum];
4591         memcpy(buf, parseList[moveNum], len);
4592         buf[len++] = '\n';
4593         buf[len] = NULLCHAR;
4594       } else {
4595         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4596       }
4597       SendToProgram(buf, cps);
4598     } else {
4599       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4600         AlphaRank(moveList[moveNum], 4);
4601         SendToProgram(moveList[moveNum], cps);
4602         AlphaRank(moveList[moveNum], 4); // and back
4603       } else
4604       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4605        * the engine. It would be nice to have a better way to identify castle
4606        * moves here. */
4607       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4608                                                                          && cps->useOOCastle) {
4609         int fromX = moveList[moveNum][0] - AAA;
4610         int fromY = moveList[moveNum][1] - ONE;
4611         int toX = moveList[moveNum][2] - AAA;
4612         int toY = moveList[moveNum][3] - ONE;
4613         if((boards[moveNum][fromY][fromX] == WhiteKing
4614             && boards[moveNum][toY][toX] == WhiteRook)
4615            || (boards[moveNum][fromY][fromX] == BlackKing
4616                && boards[moveNum][toY][toX] == BlackRook)) {
4617           if(toX > fromX) SendToProgram("O-O\n", cps);
4618           else SendToProgram("O-O-O\n", cps);
4619         }
4620         else SendToProgram(moveList[moveNum], cps);
4621       }
4622       else SendToProgram(moveList[moveNum], cps);
4623       /* End of additions by Tord */
4624     }
4625
4626     /* [HGM] setting up the opening has brought engine in force mode! */
4627     /*       Send 'go' if we are in a mode where machine should play. */
4628     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4629         (gameMode == TwoMachinesPlay   ||
4630 #if ZIPPY
4631          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4632 #endif
4633          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4634         SendToProgram("go\n", cps);
4635   if (appData.debugMode) {
4636     fprintf(debugFP, "(extra)\n");
4637   }
4638     }
4639     setboardSpoiledMachineBlack = 0;
4640 }
4641
4642 void
4643 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4644      ChessMove moveType;
4645      int fromX, fromY, toX, toY;
4646      char promoChar;
4647 {
4648     char user_move[MSG_SIZ];
4649
4650     switch (moveType) {
4651       default:
4652         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4653                 (int)moveType, fromX, fromY, toX, toY);
4654         DisplayError(user_move + strlen("say "), 0);
4655         break;
4656       case WhiteKingSideCastle:
4657       case BlackKingSideCastle:
4658       case WhiteQueenSideCastleWild:
4659       case BlackQueenSideCastleWild:
4660       /* PUSH Fabien */
4661       case WhiteHSideCastleFR:
4662       case BlackHSideCastleFR:
4663       /* POP Fabien */
4664         snprintf(user_move, MSG_SIZ, "o-o\n");
4665         break;
4666       case WhiteQueenSideCastle:
4667       case BlackQueenSideCastle:
4668       case WhiteKingSideCastleWild:
4669       case BlackKingSideCastleWild:
4670       /* PUSH Fabien */
4671       case WhiteASideCastleFR:
4672       case BlackASideCastleFR:
4673       /* POP Fabien */
4674         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4675         break;
4676       case WhiteNonPromotion:
4677       case BlackNonPromotion:
4678         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4679         break;
4680       case WhitePromotion:
4681       case BlackPromotion:
4682         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4683           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4684                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4685                 PieceToChar(WhiteFerz));
4686         else if(gameInfo.variant == VariantGreat)
4687           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4688                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4689                 PieceToChar(WhiteMan));
4690         else
4691           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4692                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4693                 promoChar);
4694         break;
4695       case WhiteDrop:
4696       case BlackDrop:
4697       drop:
4698         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4699                  ToUpper(PieceToChar((ChessSquare) fromX)),
4700                  AAA + toX, ONE + toY);
4701         break;
4702       case IllegalMove:  /* could be a variant we don't quite understand */
4703         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4704       case NormalMove:
4705       case WhiteCapturesEnPassant:
4706       case BlackCapturesEnPassant:
4707         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4708                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4709         break;
4710     }
4711     SendToICS(user_move);
4712     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4713         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4714 }
4715
4716 void
4717 UploadGameEvent()
4718 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4719     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4720     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4721     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4722         DisplayError("You cannot do this while you are playing or observing", 0);
4723         return;
4724     }
4725     if(gameMode != IcsExamining) { // is this ever not the case?
4726         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4727
4728         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4729           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4730         } else { // on FICS we must first go to general examine mode
4731           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4732         }
4733         if(gameInfo.variant != VariantNormal) {
4734             // try figure out wild number, as xboard names are not always valid on ICS
4735             for(i=1; i<=36; i++) {
4736               snprintf(buf, MSG_SIZ, "wild/%d", i);
4737                 if(StringToVariant(buf) == gameInfo.variant) break;
4738             }
4739             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4740             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4741             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4742         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4743         SendToICS(ics_prefix);
4744         SendToICS(buf);
4745         if(startedFromSetupPosition || backwardMostMove != 0) {
4746           fen = PositionToFEN(backwardMostMove, NULL);
4747           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4748             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4749             SendToICS(buf);
4750           } else { // FICS: everything has to set by separate bsetup commands
4751             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4752             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4753             SendToICS(buf);
4754             if(!WhiteOnMove(backwardMostMove)) {
4755                 SendToICS("bsetup tomove black\n");
4756             }
4757             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4758             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4759             SendToICS(buf);
4760             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4761             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4762             SendToICS(buf);
4763             i = boards[backwardMostMove][EP_STATUS];
4764             if(i >= 0) { // set e.p.
4765               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4766                 SendToICS(buf);
4767             }
4768             bsetup++;
4769           }
4770         }
4771       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4772             SendToICS("bsetup done\n"); // switch to normal examining.
4773     }
4774     for(i = backwardMostMove; i<last; i++) {
4775         char buf[20];
4776         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4777         SendToICS(buf);
4778     }
4779     SendToICS(ics_prefix);
4780     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4781 }
4782
4783 void
4784 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4785      int rf, ff, rt, ft;
4786      char promoChar;
4787      char move[7];
4788 {
4789     if (rf == DROP_RANK) {
4790       sprintf(move, "%c@%c%c\n",
4791                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4792     } else {
4793         if (promoChar == 'x' || promoChar == NULLCHAR) {
4794           sprintf(move, "%c%c%c%c\n",
4795                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4796         } else {
4797             sprintf(move, "%c%c%c%c%c\n",
4798                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4799         }
4800     }
4801 }
4802
4803 void
4804 ProcessICSInitScript(f)
4805      FILE *f;
4806 {
4807     char buf[MSG_SIZ];
4808
4809     while (fgets(buf, MSG_SIZ, f)) {
4810         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4811     }
4812
4813     fclose(f);
4814 }
4815
4816
4817 static int lastX, lastY, selectFlag, dragging;
4818
4819 void
4820 Sweep(int step)
4821 {
4822     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
4823     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
4824     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
4825     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
4826     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
4827     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
4828     do {
4829         promoSweep -= step;
4830         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
4831         else if((int)promoSweep == -1) promoSweep = WhiteKing;
4832         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
4833         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
4834         if(!step) step = 1;
4835     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
4836             appData.testLegality && (promoSweep == king ||
4837             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
4838     ChangeDragPiece(promoSweep);
4839 }
4840
4841 int PromoScroll(int x, int y)
4842 {
4843   int step = 0;
4844
4845   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
4846   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
4847   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
4848   if(!step) return FALSE;
4849   lastX = x; lastY = y;
4850   if((promoSweep < BlackPawn) == flipView) step = -step;
4851   if(step > 0) selectFlag = 1;
4852   if(!selectFlag) Sweep(step);
4853   return FALSE;
4854 }
4855
4856 void
4857 NextPiece(int step)
4858 {
4859     ChessSquare piece = boards[currentMove][toY][toX];
4860     do {
4861         pieceSweep -= step;
4862         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
4863         if((int)pieceSweep == -1) pieceSweep = BlackKing;
4864         if(!step) step = -1;
4865     } while(PieceToChar(pieceSweep) == '.');
4866     boards[currentMove][toY][toX] = pieceSweep;
4867     DrawPosition(FALSE, boards[currentMove]);
4868     boards[currentMove][toY][toX] = piece;
4869 }
4870 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4871 void
4872 AlphaRank(char *move, int n)
4873 {
4874 //    char *p = move, c; int x, y;
4875
4876     if (appData.debugMode) {
4877         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4878     }
4879
4880     if(move[1]=='*' &&
4881        move[2]>='0' && move[2]<='9' &&
4882        move[3]>='a' && move[3]<='x'    ) {
4883         move[1] = '@';
4884         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4885         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4886     } else
4887     if(move[0]>='0' && move[0]<='9' &&
4888        move[1]>='a' && move[1]<='x' &&
4889        move[2]>='0' && move[2]<='9' &&
4890        move[3]>='a' && move[3]<='x'    ) {
4891         /* input move, Shogi -> normal */
4892         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4893         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4894         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4895         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4896     } else
4897     if(move[1]=='@' &&
4898        move[3]>='0' && move[3]<='9' &&
4899        move[2]>='a' && move[2]<='x'    ) {
4900         move[1] = '*';
4901         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4902         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4903     } else
4904     if(
4905        move[0]>='a' && move[0]<='x' &&
4906        move[3]>='0' && move[3]<='9' &&
4907        move[2]>='a' && move[2]<='x'    ) {
4908          /* output move, normal -> Shogi */
4909         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4910         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4911         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4912         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4913         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4914     }
4915     if (appData.debugMode) {
4916         fprintf(debugFP, "   out = '%s'\n", move);
4917     }
4918 }
4919
4920 char yy_textstr[8000];
4921
4922 /* Parser for moves from gnuchess, ICS, or user typein box */
4923 Boolean
4924 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4925      char *move;
4926      int moveNum;
4927      ChessMove *moveType;
4928      int *fromX, *fromY, *toX, *toY;
4929      char *promoChar;
4930 {
4931     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4932
4933     switch (*moveType) {
4934       case WhitePromotion:
4935       case BlackPromotion:
4936       case WhiteNonPromotion:
4937       case BlackNonPromotion:
4938       case NormalMove:
4939       case WhiteCapturesEnPassant:
4940       case BlackCapturesEnPassant:
4941       case WhiteKingSideCastle:
4942       case WhiteQueenSideCastle:
4943       case BlackKingSideCastle:
4944       case BlackQueenSideCastle:
4945       case WhiteKingSideCastleWild:
4946       case WhiteQueenSideCastleWild:
4947       case BlackKingSideCastleWild:
4948       case BlackQueenSideCastleWild:
4949       /* Code added by Tord: */
4950       case WhiteHSideCastleFR:
4951       case WhiteASideCastleFR:
4952       case BlackHSideCastleFR:
4953       case BlackASideCastleFR:
4954       /* End of code added by Tord */
4955       case IllegalMove:         /* bug or odd chess variant */
4956         *fromX = currentMoveString[0] - AAA;
4957         *fromY = currentMoveString[1] - ONE;
4958         *toX = currentMoveString[2] - AAA;
4959         *toY = currentMoveString[3] - ONE;
4960         *promoChar = currentMoveString[4];
4961         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4962             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4963     if (appData.debugMode) {
4964         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4965     }
4966             *fromX = *fromY = *toX = *toY = 0;
4967             return FALSE;
4968         }
4969         if (appData.testLegality) {
4970           return (*moveType != IllegalMove);
4971         } else {
4972           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4973                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4974         }
4975
4976       case WhiteDrop:
4977       case BlackDrop:
4978         *fromX = *moveType == WhiteDrop ?
4979           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4980           (int) CharToPiece(ToLower(currentMoveString[0]));
4981         *fromY = DROP_RANK;
4982         *toX = currentMoveString[2] - AAA;
4983         *toY = currentMoveString[3] - ONE;
4984         *promoChar = NULLCHAR;
4985         return TRUE;
4986
4987       case AmbiguousMove:
4988       case ImpossibleMove:
4989       case EndOfFile:
4990       case ElapsedTime:
4991       case Comment:
4992       case PGNTag:
4993       case NAG:
4994       case WhiteWins:
4995       case BlackWins:
4996       case GameIsDrawn:
4997       default:
4998     if (appData.debugMode) {
4999         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5000     }
5001         /* bug? */
5002         *fromX = *fromY = *toX = *toY = 0;
5003         *promoChar = NULLCHAR;
5004         return FALSE;
5005     }
5006 }
5007
5008
5009 void
5010 ParsePV(char *pv, Boolean storeComments)
5011 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5012   int fromX, fromY, toX, toY; char promoChar;
5013   ChessMove moveType;
5014   Boolean valid;
5015   int nr = 0;
5016
5017   endPV = forwardMostMove;
5018   do {
5019     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5020     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5021     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5022 if(appData.debugMode){
5023 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5024 }
5025     if(!valid && nr == 0 &&
5026        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5027         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5028         // Hande case where played move is different from leading PV move
5029         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5030         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5031         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5032         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5033           endPV += 2; // if position different, keep this
5034           moveList[endPV-1][0] = fromX + AAA;
5035           moveList[endPV-1][1] = fromY + ONE;
5036           moveList[endPV-1][2] = toX + AAA;
5037           moveList[endPV-1][3] = toY + ONE;
5038           parseList[endPV-1][0] = NULLCHAR;
5039           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5040         }
5041       }
5042     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5043     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5044     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5045     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5046         valid++; // allow comments in PV
5047         continue;
5048     }
5049     nr++;
5050     if(endPV+1 > framePtr) break; // no space, truncate
5051     if(!valid) break;
5052     endPV++;
5053     CopyBoard(boards[endPV], boards[endPV-1]);
5054     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5055     moveList[endPV-1][0] = fromX + AAA;
5056     moveList[endPV-1][1] = fromY + ONE;
5057     moveList[endPV-1][2] = toX + AAA;
5058     moveList[endPV-1][3] = toY + ONE;
5059     moveList[endPV-1][4] = promoChar;
5060     moveList[endPV-1][5] = NULLCHAR;
5061     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5062     if(storeComments)
5063         CoordsToAlgebraic(boards[endPV - 1],
5064                              PosFlags(endPV - 1),
5065                              fromY, fromX, toY, toX, promoChar,
5066                              parseList[endPV - 1]);
5067     else
5068         parseList[endPV-1][0] = NULLCHAR;
5069   } while(valid);
5070   currentMove = endPV;
5071   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5072   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5073                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5074   DrawPosition(TRUE, boards[currentMove]);
5075 }
5076
5077 Boolean
5078 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5079 {
5080         int startPV;
5081         char *p;
5082
5083         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5084         lastX = x; lastY = y;
5085         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5086         startPV = index;
5087         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5088         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5089         index = startPV;
5090         do{ while(buf[index] && buf[index] != '\n') index++;
5091         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5092         buf[index] = 0;
5093         ParsePV(buf+startPV, FALSE);
5094         *start = startPV; *end = index-1;
5095         return TRUE;
5096 }
5097
5098 Boolean
5099 LoadPV(int x, int y)
5100 { // called on right mouse click to load PV
5101   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5102   lastX = x; lastY = y;
5103   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5104   return TRUE;
5105 }
5106
5107 void
5108 UnLoadPV()
5109 {
5110   if(endPV < 0) return;
5111   endPV = -1;
5112   currentMove = forwardMostMove;
5113   ClearPremoveHighlights();
5114   DrawPosition(TRUE, boards[currentMove]);
5115 }
5116
5117 void
5118 MovePV(int x, int y, int h)
5119 { // step through PV based on mouse coordinates (called on mouse move)
5120   int margin = h>>3, step = 0;
5121
5122   // we must somehow check if right button is still down (might be released off board!)
5123   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5124   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5125   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5126   if(!step) return;
5127   lastX = x; lastY = y;
5128
5129   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5130   if(endPV < 0) return;
5131   if(y < margin) step = 1; else
5132   if(y > h - margin) step = -1;
5133   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5134   currentMove += step;
5135   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5136   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5137                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5138   DrawPosition(FALSE, boards[currentMove]);
5139 }
5140
5141
5142 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5143 // All positions will have equal probability, but the current method will not provide a unique
5144 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5145 #define DARK 1
5146 #define LITE 2
5147 #define ANY 3
5148
5149 int squaresLeft[4];
5150 int piecesLeft[(int)BlackPawn];
5151 int seed, nrOfShuffles;
5152
5153 void GetPositionNumber()
5154 {       // sets global variable seed
5155         int i;
5156
5157         seed = appData.defaultFrcPosition;
5158         if(seed < 0) { // randomize based on time for negative FRC position numbers
5159                 for(i=0; i<50; i++) seed += random();
5160                 seed = random() ^ random() >> 8 ^ random() << 8;
5161                 if(seed<0) seed = -seed;
5162         }
5163 }
5164
5165 int put(Board board, int pieceType, int rank, int n, int shade)
5166 // put the piece on the (n-1)-th empty squares of the given shade
5167 {
5168         int i;
5169
5170         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5171                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5172                         board[rank][i] = (ChessSquare) pieceType;
5173                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5174                         squaresLeft[ANY]--;
5175                         piecesLeft[pieceType]--;
5176                         return i;
5177                 }
5178         }
5179         return -1;
5180 }
5181
5182
5183 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5184 // calculate where the next piece goes, (any empty square), and put it there
5185 {
5186         int i;
5187
5188         i = seed % squaresLeft[shade];
5189         nrOfShuffles *= squaresLeft[shade];
5190         seed /= squaresLeft[shade];
5191         put(board, pieceType, rank, i, shade);
5192 }
5193
5194 void AddTwoPieces(Board board, int pieceType, int rank)
5195 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5196 {
5197         int i, n=squaresLeft[ANY], j=n-1, k;
5198
5199         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5200         i = seed % k;  // pick one
5201         nrOfShuffles *= k;
5202         seed /= k;
5203         while(i >= j) i -= j--;
5204         j = n - 1 - j; i += j;
5205         put(board, pieceType, rank, j, ANY);
5206         put(board, pieceType, rank, i, ANY);
5207 }
5208
5209 void SetUpShuffle(Board board, int number)
5210 {
5211         int i, p, first=1;
5212
5213         GetPositionNumber(); nrOfShuffles = 1;
5214
5215         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5216         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5217         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5218
5219         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5220
5221         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5222             p = (int) board[0][i];
5223             if(p < (int) BlackPawn) piecesLeft[p] ++;
5224             board[0][i] = EmptySquare;
5225         }
5226
5227         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5228             // shuffles restricted to allow normal castling put KRR first
5229             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5230                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5231             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5232                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5233             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5234                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5235             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5236                 put(board, WhiteRook, 0, 0, ANY);
5237             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5238         }
5239
5240         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5241             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5242             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5243                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5244                 while(piecesLeft[p] >= 2) {
5245                     AddOnePiece(board, p, 0, LITE);
5246                     AddOnePiece(board, p, 0, DARK);
5247                 }
5248                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5249             }
5250
5251         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5252             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5253             // but we leave King and Rooks for last, to possibly obey FRC restriction
5254             if(p == (int)WhiteRook) continue;
5255             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5256             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5257         }
5258
5259         // now everything is placed, except perhaps King (Unicorn) and Rooks
5260
5261         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5262             // Last King gets castling rights
5263             while(piecesLeft[(int)WhiteUnicorn]) {
5264                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5265                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5266             }
5267
5268             while(piecesLeft[(int)WhiteKing]) {
5269                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5270                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5271             }
5272
5273
5274         } else {
5275             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5276             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5277         }
5278
5279         // Only Rooks can be left; simply place them all
5280         while(piecesLeft[(int)WhiteRook]) {
5281                 i = put(board, WhiteRook, 0, 0, ANY);
5282                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5283                         if(first) {
5284                                 first=0;
5285                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5286                         }
5287                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5288                 }
5289         }
5290         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5291             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5292         }
5293
5294         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5295 }
5296
5297 int SetCharTable( char *table, const char * map )
5298 /* [HGM] moved here from winboard.c because of its general usefulness */
5299 /*       Basically a safe strcpy that uses the last character as King */
5300 {
5301     int result = FALSE; int NrPieces;
5302
5303     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5304                     && NrPieces >= 12 && !(NrPieces&1)) {
5305         int i; /* [HGM] Accept even length from 12 to 34 */
5306
5307         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5308         for( i=0; i<NrPieces/2-1; i++ ) {
5309             table[i] = map[i];
5310             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5311         }
5312         table[(int) WhiteKing]  = map[NrPieces/2-1];
5313         table[(int) BlackKing]  = map[NrPieces-1];
5314
5315         result = TRUE;
5316     }
5317
5318     return result;
5319 }
5320
5321 void Prelude(Board board)
5322 {       // [HGM] superchess: random selection of exo-pieces
5323         int i, j, k; ChessSquare p;
5324         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5325
5326         GetPositionNumber(); // use FRC position number
5327
5328         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5329             SetCharTable(pieceToChar, appData.pieceToCharTable);
5330             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5331                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5332         }
5333
5334         j = seed%4;                 seed /= 4;
5335         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5336         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5337         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5338         j = seed%3 + (seed%3 >= j); seed /= 3;
5339         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5340         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5341         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5342         j = seed%3;                 seed /= 3;
5343         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5344         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5345         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5346         j = seed%2 + (seed%2 >= j); seed /= 2;
5347         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5348         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5349         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5350         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5351         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5352         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5353         put(board, exoPieces[0],    0, 0, ANY);
5354         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5355 }
5356
5357 void
5358 InitPosition(redraw)
5359      int redraw;
5360 {
5361     ChessSquare (* pieces)[BOARD_FILES];
5362     int i, j, pawnRow, overrule,
5363     oldx = gameInfo.boardWidth,
5364     oldy = gameInfo.boardHeight,
5365     oldh = gameInfo.holdingsWidth;
5366     static int oldv;
5367
5368     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5369
5370     /* [AS] Initialize pv info list [HGM] and game status */
5371     {
5372         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5373             pvInfoList[i].depth = 0;
5374             boards[i][EP_STATUS] = EP_NONE;
5375             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5376         }
5377
5378         initialRulePlies = 0; /* 50-move counter start */
5379
5380         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5381         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5382     }
5383
5384
5385     /* [HGM] logic here is completely changed. In stead of full positions */
5386     /* the initialized data only consist of the two backranks. The switch */
5387     /* selects which one we will use, which is than copied to the Board   */
5388     /* initialPosition, which for the rest is initialized by Pawns and    */
5389     /* empty squares. This initial position is then copied to boards[0],  */
5390     /* possibly after shuffling, so that it remains available.            */
5391
5392     gameInfo.holdingsWidth = 0; /* default board sizes */
5393     gameInfo.boardWidth    = 8;
5394     gameInfo.boardHeight   = 8;
5395     gameInfo.holdingsSize  = 0;
5396     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5397     for(i=0; i<BOARD_FILES-2; i++)
5398       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5399     initialPosition[EP_STATUS] = EP_NONE;
5400     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5401     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5402          SetCharTable(pieceNickName, appData.pieceNickNames);
5403     else SetCharTable(pieceNickName, "............");
5404     pieces = FIDEArray;
5405
5406     switch (gameInfo.variant) {
5407     case VariantFischeRandom:
5408       shuffleOpenings = TRUE;
5409     default:
5410       break;
5411     case VariantShatranj:
5412       pieces = ShatranjArray;
5413       nrCastlingRights = 0;
5414       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5415       break;
5416     case VariantMakruk:
5417       pieces = makrukArray;
5418       nrCastlingRights = 0;
5419       startedFromSetupPosition = TRUE;
5420       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5421       break;
5422     case VariantTwoKings:
5423       pieces = twoKingsArray;
5424       break;
5425     case VariantCapaRandom:
5426       shuffleOpenings = TRUE;
5427     case VariantCapablanca:
5428       pieces = CapablancaArray;
5429       gameInfo.boardWidth = 10;
5430       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5431       break;
5432     case VariantGothic:
5433       pieces = GothicArray;
5434       gameInfo.boardWidth = 10;
5435       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5436       break;
5437     case VariantSChess:
5438       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5439       gameInfo.holdingsSize = 7;
5440       break;
5441     case VariantJanus:
5442       pieces = JanusArray;
5443       gameInfo.boardWidth = 10;
5444       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5445       nrCastlingRights = 6;
5446         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5447         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5448         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5449         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5450         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5451         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5452       break;
5453     case VariantFalcon:
5454       pieces = FalconArray;
5455       gameInfo.boardWidth = 10;
5456       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5457       break;
5458     case VariantXiangqi:
5459       pieces = XiangqiArray;
5460       gameInfo.boardWidth  = 9;
5461       gameInfo.boardHeight = 10;
5462       nrCastlingRights = 0;
5463       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5464       break;
5465     case VariantShogi:
5466       pieces = ShogiArray;
5467       gameInfo.boardWidth  = 9;
5468       gameInfo.boardHeight = 9;
5469       gameInfo.holdingsSize = 7;
5470       nrCastlingRights = 0;
5471       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5472       break;
5473     case VariantCourier:
5474       pieces = CourierArray;
5475       gameInfo.boardWidth  = 12;
5476       nrCastlingRights = 0;
5477       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5478       break;
5479     case VariantKnightmate:
5480       pieces = KnightmateArray;
5481       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5482       break;
5483     case VariantSpartan:
5484       pieces = SpartanArray;
5485       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5486       break;
5487     case VariantFairy:
5488       pieces = fairyArray;
5489       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5490       break;
5491     case VariantGreat:
5492       pieces = GreatArray;
5493       gameInfo.boardWidth = 10;
5494       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5495       gameInfo.holdingsSize = 8;
5496       break;
5497     case VariantSuper:
5498       pieces = FIDEArray;
5499       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5500       gameInfo.holdingsSize = 8;
5501       startedFromSetupPosition = TRUE;
5502       break;
5503     case VariantCrazyhouse:
5504     case VariantBughouse:
5505       pieces = FIDEArray;
5506       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5507       gameInfo.holdingsSize = 5;
5508       break;
5509     case VariantWildCastle:
5510       pieces = FIDEArray;
5511       /* !!?shuffle with kings guaranteed to be on d or e file */
5512       shuffleOpenings = 1;
5513       break;
5514     case VariantNoCastle:
5515       pieces = FIDEArray;
5516       nrCastlingRights = 0;
5517       /* !!?unconstrained back-rank shuffle */
5518       shuffleOpenings = 1;
5519       break;
5520     }
5521
5522     overrule = 0;
5523     if(appData.NrFiles >= 0) {
5524         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5525         gameInfo.boardWidth = appData.NrFiles;
5526     }
5527     if(appData.NrRanks >= 0) {
5528         gameInfo.boardHeight = appData.NrRanks;
5529     }
5530     if(appData.holdingsSize >= 0) {
5531         i = appData.holdingsSize;
5532         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5533         gameInfo.holdingsSize = i;
5534     }
5535     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5536     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5537         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5538
5539     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5540     if(pawnRow < 1) pawnRow = 1;
5541     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5542
5543     /* User pieceToChar list overrules defaults */
5544     if(appData.pieceToCharTable != NULL)
5545         SetCharTable(pieceToChar, appData.pieceToCharTable);
5546
5547     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5548
5549         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5550             s = (ChessSquare) 0; /* account holding counts in guard band */
5551         for( i=0; i<BOARD_HEIGHT; i++ )
5552             initialPosition[i][j] = s;
5553
5554         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5555         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5556         initialPosition[pawnRow][j] = WhitePawn;
5557         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5558         if(gameInfo.variant == VariantXiangqi) {
5559             if(j&1) {
5560                 initialPosition[pawnRow][j] =
5561                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5562                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5563                    initialPosition[2][j] = WhiteCannon;
5564                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5565                 }
5566             }
5567         }
5568         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5569     }
5570     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5571
5572             j=BOARD_LEFT+1;
5573             initialPosition[1][j] = WhiteBishop;
5574             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5575             j=BOARD_RGHT-2;
5576             initialPosition[1][j] = WhiteRook;
5577             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5578     }
5579
5580     if( nrCastlingRights == -1) {
5581         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5582         /*       This sets default castling rights from none to normal corners   */
5583         /* Variants with other castling rights must set them themselves above    */
5584         nrCastlingRights = 6;
5585
5586         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5587         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5588         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5589         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5590         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5591         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5592      }
5593
5594      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5595      if(gameInfo.variant == VariantGreat) { // promotion commoners
5596         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5597         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5598         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5599         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5600      }
5601      if( gameInfo.variant == VariantSChess ) {
5602       initialPosition[1][0] = BlackMarshall;
5603       initialPosition[2][0] = BlackAngel;
5604       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5605       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5606       initialPosition[1][1] = initialPosition[2][1] = 
5607       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5608      }
5609   if (appData.debugMode) {
5610     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5611   }
5612     if(shuffleOpenings) {
5613         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5614         startedFromSetupPosition = TRUE;
5615     }
5616     if(startedFromPositionFile) {
5617       /* [HGM] loadPos: use PositionFile for every new game */
5618       CopyBoard(initialPosition, filePosition);
5619       for(i=0; i<nrCastlingRights; i++)
5620           initialRights[i] = filePosition[CASTLING][i];
5621       startedFromSetupPosition = TRUE;
5622     }
5623
5624     CopyBoard(boards[0], initialPosition);
5625
5626     if(oldx != gameInfo.boardWidth ||
5627        oldy != gameInfo.boardHeight ||
5628        oldv != gameInfo.variant ||
5629        oldh != gameInfo.holdingsWidth
5630                                          )
5631             InitDrawingSizes(-2 ,0);
5632
5633     oldv = gameInfo.variant;
5634     if (redraw)
5635       DrawPosition(TRUE, boards[currentMove]);
5636 }
5637
5638 void
5639 SendBoard(cps, moveNum)
5640      ChessProgramState *cps;
5641      int moveNum;
5642 {
5643     char message[MSG_SIZ];
5644
5645     if (cps->useSetboard) {
5646       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5647       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5648       SendToProgram(message, cps);
5649       free(fen);
5650
5651     } else {
5652       ChessSquare *bp;
5653       int i, j;
5654       /* Kludge to set black to move, avoiding the troublesome and now
5655        * deprecated "black" command.
5656        */
5657       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5658         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5659
5660       SendToProgram("edit\n", cps);
5661       SendToProgram("#\n", cps);
5662       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5663         bp = &boards[moveNum][i][BOARD_LEFT];
5664         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5665           if ((int) *bp < (int) BlackPawn) {
5666             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5667                     AAA + j, ONE + i);
5668             if(message[0] == '+' || message[0] == '~') {
5669               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5670                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5671                         AAA + j, ONE + i);
5672             }
5673             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5674                 message[1] = BOARD_RGHT   - 1 - j + '1';
5675                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5676             }
5677             SendToProgram(message, cps);
5678           }
5679         }
5680       }
5681
5682       SendToProgram("c\n", cps);
5683       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5684         bp = &boards[moveNum][i][BOARD_LEFT];
5685         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5686           if (((int) *bp != (int) EmptySquare)
5687               && ((int) *bp >= (int) BlackPawn)) {
5688             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5689                     AAA + j, ONE + i);
5690             if(message[0] == '+' || message[0] == '~') {
5691               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5692                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5693                         AAA + j, ONE + i);
5694             }
5695             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5696                 message[1] = BOARD_RGHT   - 1 - j + '1';
5697                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5698             }
5699             SendToProgram(message, cps);
5700           }
5701         }
5702       }
5703
5704       SendToProgram(".\n", cps);
5705     }
5706     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5707 }
5708
5709 ChessSquare
5710 DefaultPromoChoice(int white)
5711 {
5712     ChessSquare result;
5713     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5714         result = WhiteFerz; // no choice
5715     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5716         result= WhiteKing; // in Suicide Q is the last thing we want
5717     else if(gameInfo.variant == VariantSpartan)
5718         result = white ? WhiteQueen : WhiteAngel;
5719     else result = WhiteQueen;
5720     if(!white) result = WHITE_TO_BLACK result;
5721     return result;
5722 }
5723
5724 static int autoQueen; // [HGM] oneclick
5725
5726 int
5727 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5728 {
5729     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5730     /* [HGM] add Shogi promotions */
5731     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5732     ChessSquare piece;
5733     ChessMove moveType;
5734     Boolean premove;
5735
5736     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5737     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5738
5739     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5740       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5741         return FALSE;
5742
5743     piece = boards[currentMove][fromY][fromX];
5744     if(gameInfo.variant == VariantShogi) {
5745         promotionZoneSize = BOARD_HEIGHT/3;
5746         highestPromotingPiece = (int)WhiteFerz;
5747     } else if(gameInfo.variant == VariantMakruk) {
5748         promotionZoneSize = 3;
5749     }
5750
5751     // Treat Lance as Pawn when it is not representing Amazon
5752     if(gameInfo.variant != VariantSuper) {
5753         if(piece == WhiteLance) piece = WhitePawn; else
5754         if(piece == BlackLance) piece = BlackPawn;
5755     }
5756
5757     // next weed out all moves that do not touch the promotion zone at all
5758     if((int)piece >= BlackPawn) {
5759         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5760              return FALSE;
5761         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5762     } else {
5763         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5764            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5765     }
5766
5767     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5768
5769     // weed out mandatory Shogi promotions
5770     if(gameInfo.variant == VariantShogi) {
5771         if(piece >= BlackPawn) {
5772             if(toY == 0 && piece == BlackPawn ||
5773                toY == 0 && piece == BlackQueen ||
5774                toY <= 1 && piece == BlackKnight) {
5775                 *promoChoice = '+';
5776                 return FALSE;
5777             }
5778         } else {
5779             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5780                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5781                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5782                 *promoChoice = '+';
5783                 return FALSE;
5784             }
5785         }
5786     }
5787
5788     // weed out obviously illegal Pawn moves
5789     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5790         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5791         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5792         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5793         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5794         // note we are not allowed to test for valid (non-)capture, due to premove
5795     }
5796
5797     // we either have a choice what to promote to, or (in Shogi) whether to promote
5798     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5799         *promoChoice = PieceToChar(BlackFerz);  // no choice
5800         return FALSE;
5801     }
5802     // no sense asking what we must promote to if it is going to explode...
5803     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5804         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5805         return FALSE;
5806     }
5807     // give caller the default choice even if we will not make it
5808     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
5809     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
5810     if(appData.sweepSelect && gameInfo.variant != VariantGreat
5811                            && gameInfo.variant != VariantShogi
5812                            && gameInfo.variant != VariantSuper) return FALSE;
5813     if(autoQueen) return FALSE; // predetermined
5814
5815     // suppress promotion popup on illegal moves that are not premoves
5816     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5817               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5818     if(appData.testLegality && !premove) {
5819         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5820                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5821         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5822             return FALSE;
5823     }
5824
5825     return TRUE;
5826 }
5827
5828 int
5829 InPalace(row, column)
5830      int row, column;
5831 {   /* [HGM] for Xiangqi */
5832     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5833          column < (BOARD_WIDTH + 4)/2 &&
5834          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5835     return FALSE;
5836 }
5837
5838 int
5839 PieceForSquare (x, y)
5840      int x;
5841      int y;
5842 {
5843   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5844      return -1;
5845   else
5846      return boards[currentMove][y][x];
5847 }
5848
5849 int
5850 OKToStartUserMove(x, y)
5851      int x, y;
5852 {
5853     ChessSquare from_piece;
5854     int white_piece;
5855
5856     if (matchMode) return FALSE;
5857     if (gameMode == EditPosition) return TRUE;
5858
5859     if (x >= 0 && y >= 0)
5860       from_piece = boards[currentMove][y][x];
5861     else
5862       from_piece = EmptySquare;
5863
5864     if (from_piece == EmptySquare) return FALSE;
5865
5866     white_piece = (int)from_piece >= (int)WhitePawn &&
5867       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5868
5869     switch (gameMode) {
5870       case PlayFromGameFile:
5871       case AnalyzeFile:
5872       case TwoMachinesPlay:
5873       case EndOfGame:
5874         return FALSE;
5875
5876       case IcsObserving:
5877       case IcsIdle:
5878         return FALSE;
5879
5880       case MachinePlaysWhite:
5881       case IcsPlayingBlack:
5882         if (appData.zippyPlay) return FALSE;
5883         if (white_piece) {
5884             DisplayMoveError(_("You are playing Black"));
5885             return FALSE;
5886         }
5887         break;
5888
5889       case MachinePlaysBlack:
5890       case IcsPlayingWhite:
5891         if (appData.zippyPlay) return FALSE;
5892         if (!white_piece) {
5893             DisplayMoveError(_("You are playing White"));
5894             return FALSE;
5895         }
5896         break;
5897
5898       case EditGame:
5899         if (!white_piece && WhiteOnMove(currentMove)) {
5900             DisplayMoveError(_("It is White's turn"));
5901             return FALSE;
5902         }
5903         if (white_piece && !WhiteOnMove(currentMove)) {
5904             DisplayMoveError(_("It is Black's turn"));
5905             return FALSE;
5906         }
5907         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5908             /* Editing correspondence game history */
5909             /* Could disallow this or prompt for confirmation */
5910             cmailOldMove = -1;
5911         }
5912         break;
5913
5914       case BeginningOfGame:
5915         if (appData.icsActive) return FALSE;
5916         if (!appData.noChessProgram) {
5917             if (!white_piece) {
5918                 DisplayMoveError(_("You are playing White"));
5919                 return FALSE;
5920             }
5921         }
5922         break;
5923
5924       case Training:
5925         if (!white_piece && WhiteOnMove(currentMove)) {
5926             DisplayMoveError(_("It is White's turn"));
5927             return FALSE;
5928         }
5929         if (white_piece && !WhiteOnMove(currentMove)) {
5930             DisplayMoveError(_("It is Black's turn"));
5931             return FALSE;
5932         }
5933         break;
5934
5935       default:
5936       case IcsExamining:
5937         break;
5938     }
5939     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5940         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5941         && gameMode != AnalyzeFile && gameMode != Training) {
5942         DisplayMoveError(_("Displayed position is not current"));
5943         return FALSE;
5944     }
5945     return TRUE;
5946 }
5947
5948 Boolean
5949 OnlyMove(int *x, int *y, Boolean captures) {
5950     DisambiguateClosure cl;
5951     if (appData.zippyPlay) return FALSE;
5952     switch(gameMode) {
5953       case MachinePlaysBlack:
5954       case IcsPlayingWhite:
5955       case BeginningOfGame:
5956         if(!WhiteOnMove(currentMove)) return FALSE;
5957         break;
5958       case MachinePlaysWhite:
5959       case IcsPlayingBlack:
5960         if(WhiteOnMove(currentMove)) return FALSE;
5961         break;
5962       case EditGame:
5963         break;
5964       default:
5965         return FALSE;
5966     }
5967     cl.pieceIn = EmptySquare;
5968     cl.rfIn = *y;
5969     cl.ffIn = *x;
5970     cl.rtIn = -1;
5971     cl.ftIn = -1;
5972     cl.promoCharIn = NULLCHAR;
5973     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5974     if( cl.kind == NormalMove ||
5975         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5976         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5977         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5978       fromX = cl.ff;
5979       fromY = cl.rf;
5980       *x = cl.ft;
5981       *y = cl.rt;
5982       return TRUE;
5983     }
5984     if(cl.kind != ImpossibleMove) return FALSE;
5985     cl.pieceIn = EmptySquare;
5986     cl.rfIn = -1;
5987     cl.ffIn = -1;
5988     cl.rtIn = *y;
5989     cl.ftIn = *x;
5990     cl.promoCharIn = NULLCHAR;
5991     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5992     if( cl.kind == NormalMove ||
5993         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5994         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5995         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5996       fromX = cl.ff;
5997       fromY = cl.rf;
5998       *x = cl.ft;
5999       *y = cl.rt;
6000       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6001       return TRUE;
6002     }
6003     return FALSE;
6004 }
6005
6006 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6007 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6008 int lastLoadGameUseList = FALSE;
6009 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6010 ChessMove lastLoadGameStart = EndOfFile;
6011
6012 void
6013 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6014      int fromX, fromY, toX, toY;
6015      int promoChar;
6016 {
6017     ChessMove moveType;
6018     ChessSquare pdown, pup;
6019
6020     /* Check if the user is playing in turn.  This is complicated because we
6021        let the user "pick up" a piece before it is his turn.  So the piece he
6022        tried to pick up may have been captured by the time he puts it down!
6023        Therefore we use the color the user is supposed to be playing in this
6024        test, not the color of the piece that is currently on the starting
6025        square---except in EditGame mode, where the user is playing both
6026        sides; fortunately there the capture race can't happen.  (It can
6027        now happen in IcsExamining mode, but that's just too bad.  The user
6028        will get a somewhat confusing message in that case.)
6029        */
6030
6031     switch (gameMode) {
6032       case PlayFromGameFile:
6033       case AnalyzeFile:
6034       case TwoMachinesPlay:
6035       case EndOfGame:
6036       case IcsObserving:
6037       case IcsIdle:
6038         /* We switched into a game mode where moves are not accepted,
6039            perhaps while the mouse button was down. */
6040         return;
6041
6042       case MachinePlaysWhite:
6043         /* User is moving for Black */
6044         if (WhiteOnMove(currentMove)) {
6045             DisplayMoveError(_("It is White's turn"));
6046             return;
6047         }
6048         break;
6049
6050       case MachinePlaysBlack:
6051         /* User is moving for White */
6052         if (!WhiteOnMove(currentMove)) {
6053             DisplayMoveError(_("It is Black's turn"));
6054             return;
6055         }
6056         break;
6057
6058       case EditGame:
6059       case IcsExamining:
6060       case BeginningOfGame:
6061       case AnalyzeMode:
6062       case Training:
6063         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6064         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6065             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6066             /* User is moving for Black */
6067             if (WhiteOnMove(currentMove)) {
6068                 DisplayMoveError(_("It is White's turn"));
6069                 return;
6070             }
6071         } else {
6072             /* User is moving for White */
6073             if (!WhiteOnMove(currentMove)) {
6074                 DisplayMoveError(_("It is Black's turn"));
6075                 return;
6076             }
6077         }
6078         break;
6079
6080       case IcsPlayingBlack:
6081         /* User is moving for Black */
6082         if (WhiteOnMove(currentMove)) {
6083             if (!appData.premove) {
6084                 DisplayMoveError(_("It is White's turn"));
6085             } else if (toX >= 0 && toY >= 0) {
6086                 premoveToX = toX;
6087                 premoveToY = toY;
6088                 premoveFromX = fromX;
6089                 premoveFromY = fromY;
6090                 premovePromoChar = promoChar;
6091                 gotPremove = 1;
6092                 if (appData.debugMode)
6093                     fprintf(debugFP, "Got premove: fromX %d,"
6094                             "fromY %d, toX %d, toY %d\n",
6095                             fromX, fromY, toX, toY);
6096             }
6097             return;
6098         }
6099         break;
6100
6101       case IcsPlayingWhite:
6102         /* User is moving for White */
6103         if (!WhiteOnMove(currentMove)) {
6104             if (!appData.premove) {
6105                 DisplayMoveError(_("It is Black's turn"));
6106             } else if (toX >= 0 && toY >= 0) {
6107                 premoveToX = toX;
6108                 premoveToY = toY;
6109                 premoveFromX = fromX;
6110                 premoveFromY = fromY;
6111                 premovePromoChar = promoChar;
6112                 gotPremove = 1;
6113                 if (appData.debugMode)
6114                     fprintf(debugFP, "Got premove: fromX %d,"
6115                             "fromY %d, toX %d, toY %d\n",
6116                             fromX, fromY, toX, toY);
6117             }
6118             return;
6119         }
6120         break;
6121
6122       default:
6123         break;
6124
6125       case EditPosition:
6126         /* EditPosition, empty square, or different color piece;
6127            click-click move is possible */
6128         if (toX == -2 || toY == -2) {
6129             boards[0][fromY][fromX] = EmptySquare;
6130             DrawPosition(FALSE, boards[currentMove]);
6131             return;
6132         } else if (toX >= 0 && toY >= 0) {
6133             boards[0][toY][toX] = boards[0][fromY][fromX];
6134             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6135                 if(boards[0][fromY][0] != EmptySquare) {
6136                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6137                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6138                 }
6139             } else
6140             if(fromX == BOARD_RGHT+1) {
6141                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6142                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6143                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6144                 }
6145             } else
6146             boards[0][fromY][fromX] = EmptySquare;
6147             DrawPosition(FALSE, boards[currentMove]);
6148             return;
6149         }
6150         return;
6151     }
6152
6153     if(toX < 0 || toY < 0) return;
6154     pdown = boards[currentMove][fromY][fromX];
6155     pup = boards[currentMove][toY][toX];
6156
6157     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6158     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6159          if( pup != EmptySquare ) return;
6160          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6161            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6162                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6163            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6164            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6165            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6166            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6167          fromY = DROP_RANK;
6168     }
6169
6170     /* [HGM] always test for legality, to get promotion info */
6171     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6172                                          fromY, fromX, toY, toX, promoChar);
6173     /* [HGM] but possibly ignore an IllegalMove result */
6174     if (appData.testLegality) {
6175         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6176             DisplayMoveError(_("Illegal move"));
6177             return;
6178         }
6179     }
6180
6181     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6182 }
6183
6184 /* Common tail of UserMoveEvent and DropMenuEvent */
6185 int
6186 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6187      ChessMove moveType;
6188      int fromX, fromY, toX, toY;
6189      /*char*/int promoChar;
6190 {
6191     char *bookHit = 0;
6192
6193     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6194         // [HGM] superchess: suppress promotions to non-available piece
6195         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6196         if(WhiteOnMove(currentMove)) {
6197             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6198         } else {
6199             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6200         }
6201     }
6202
6203     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6204        move type in caller when we know the move is a legal promotion */
6205     if(moveType == NormalMove && promoChar)
6206         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6207
6208     /* [HGM] <popupFix> The following if has been moved here from
6209        UserMoveEvent(). Because it seemed to belong here (why not allow
6210        piece drops in training games?), and because it can only be
6211        performed after it is known to what we promote. */
6212     if (gameMode == Training) {
6213       /* compare the move played on the board to the next move in the
6214        * game. If they match, display the move and the opponent's response.
6215        * If they don't match, display an error message.
6216        */
6217       int saveAnimate;
6218       Board testBoard;
6219       CopyBoard(testBoard, boards[currentMove]);
6220       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6221
6222       if (CompareBoards(testBoard, boards[currentMove+1])) {
6223         ForwardInner(currentMove+1);
6224
6225         /* Autoplay the opponent's response.
6226          * if appData.animate was TRUE when Training mode was entered,
6227          * the response will be animated.
6228          */
6229         saveAnimate = appData.animate;
6230         appData.animate = animateTraining;
6231         ForwardInner(currentMove+1);
6232         appData.animate = saveAnimate;
6233
6234         /* check for the end of the game */
6235         if (currentMove >= forwardMostMove) {
6236           gameMode = PlayFromGameFile;
6237           ModeHighlight();
6238           SetTrainingModeOff();
6239           DisplayInformation(_("End of game"));
6240         }
6241       } else {
6242         DisplayError(_("Incorrect move"), 0);
6243       }
6244       return 1;
6245     }
6246
6247   /* Ok, now we know that the move is good, so we can kill
6248      the previous line in Analysis Mode */
6249   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6250                                 && currentMove < forwardMostMove) {
6251     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6252     else forwardMostMove = currentMove;
6253   }
6254
6255   /* If we need the chess program but it's dead, restart it */
6256   ResurrectChessProgram();
6257
6258   /* A user move restarts a paused game*/
6259   if (pausing)
6260     PauseEvent();
6261
6262   thinkOutput[0] = NULLCHAR;
6263
6264   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6265
6266   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6267     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6268     return 1;
6269   }
6270
6271   if (gameMode == BeginningOfGame) {
6272     if (appData.noChessProgram) {
6273       gameMode = EditGame;
6274       SetGameInfo();
6275     } else {
6276       char buf[MSG_SIZ];
6277       gameMode = MachinePlaysBlack;
6278       StartClocks();
6279       SetGameInfo();
6280       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6281       DisplayTitle(buf);
6282       if (first.sendName) {
6283         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6284         SendToProgram(buf, &first);
6285       }
6286       StartClocks();
6287     }
6288     ModeHighlight();
6289   }
6290
6291   /* Relay move to ICS or chess engine */
6292   if (appData.icsActive) {
6293     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6294         gameMode == IcsExamining) {
6295       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6296         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6297         SendToICS("draw ");
6298         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6299       }
6300       // also send plain move, in case ICS does not understand atomic claims
6301       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6302       ics_user_moved = 1;
6303     }
6304   } else {
6305     if (first.sendTime && (gameMode == BeginningOfGame ||
6306                            gameMode == MachinePlaysWhite ||
6307                            gameMode == MachinePlaysBlack)) {
6308       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6309     }
6310     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6311          // [HGM] book: if program might be playing, let it use book
6312         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6313         first.maybeThinking = TRUE;
6314     } else SendMoveToProgram(forwardMostMove-1, &first);
6315     if (currentMove == cmailOldMove + 1) {
6316       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6317     }
6318   }
6319
6320   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6321
6322   switch (gameMode) {
6323   case EditGame:
6324     if(appData.testLegality)
6325     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6326     case MT_NONE:
6327     case MT_CHECK:
6328       break;
6329     case MT_CHECKMATE:
6330     case MT_STAINMATE:
6331       if (WhiteOnMove(currentMove)) {
6332         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6333       } else {
6334         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6335       }
6336       break;
6337     case MT_STALEMATE:
6338       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6339       break;
6340     }
6341     break;
6342
6343   case MachinePlaysBlack:
6344   case MachinePlaysWhite:
6345     /* disable certain menu options while machine is thinking */
6346     SetMachineThinkingEnables();
6347     break;
6348
6349   default:
6350     break;
6351   }
6352
6353   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6354   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6355
6356   if(bookHit) { // [HGM] book: simulate book reply
6357         static char bookMove[MSG_SIZ]; // a bit generous?
6358
6359         programStats.nodes = programStats.depth = programStats.time =
6360         programStats.score = programStats.got_only_move = 0;
6361         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6362
6363         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6364         strcat(bookMove, bookHit);
6365         HandleMachineMove(bookMove, &first);
6366   }
6367   return 1;
6368 }
6369
6370 void
6371 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6372      Board board;
6373      int flags;
6374      ChessMove kind;
6375      int rf, ff, rt, ft;
6376      VOIDSTAR closure;
6377 {
6378     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6379     Markers *m = (Markers *) closure;
6380     if(rf == fromY && ff == fromX)
6381         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6382                          || kind == WhiteCapturesEnPassant
6383                          || kind == BlackCapturesEnPassant);
6384     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6385 }
6386
6387 void
6388 MarkTargetSquares(int clear)
6389 {
6390   int x, y;
6391   if(!appData.markers || !appData.highlightDragging ||
6392      !appData.testLegality || gameMode == EditPosition) return;
6393   if(clear) {
6394     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6395   } else {
6396     int capt = 0;
6397     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6398     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6399       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6400       if(capt)
6401       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6402     }
6403   }
6404   DrawPosition(TRUE, NULL);
6405 }
6406
6407 int
6408 Explode(Board board, int fromX, int fromY, int toX, int toY)
6409 {
6410     if(gameInfo.variant == VariantAtomic &&
6411        (board[toY][toX] != EmptySquare ||                     // capture?
6412         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6413                          board[fromY][fromX] == BlackPawn   )
6414       )) {
6415         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6416         return TRUE;
6417     }
6418     return FALSE;
6419 }
6420
6421 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6422
6423 int CanPromote(ChessSquare piece, int y)
6424 {
6425         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6426         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6427         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6428            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6429            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6430                                                   gameInfo.variant == VariantMakruk) return FALSE;
6431         return (piece == BlackPawn && y == 1 ||
6432                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6433                 piece == BlackLance && y == 1 ||
6434                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6435 }
6436
6437 void LeftClick(ClickType clickType, int xPix, int yPix)
6438 {
6439     int x, y;
6440     Boolean saveAnimate;
6441     static int second = 0, promotionChoice = 0;
6442     char promoChoice = NULLCHAR;
6443     ChessSquare piece;
6444
6445     if(appData.seekGraph && appData.icsActive && loggedOn &&
6446         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6447         SeekGraphClick(clickType, xPix, yPix, 0);
6448         return;
6449     }
6450
6451     if (clickType == Press) ErrorPopDown();
6452     MarkTargetSquares(1);
6453
6454     x = EventToSquare(xPix, BOARD_WIDTH);
6455     y = EventToSquare(yPix, BOARD_HEIGHT);
6456     if (!flipView && y >= 0) {
6457         y = BOARD_HEIGHT - 1 - y;
6458     }
6459     if (flipView && x >= 0) {
6460         x = BOARD_WIDTH - 1 - x;
6461     }
6462
6463     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6464         defaultPromoChoice = promoSweep;
6465         promoSweep = EmptySquare;   // terminate sweep
6466         promoDefaultAltered = TRUE;
6467         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6468     }
6469
6470     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6471         if(clickType == Release) return; // ignore upclick of click-click destination
6472         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6473         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6474         if(gameInfo.holdingsWidth &&
6475                 (WhiteOnMove(currentMove)
6476                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6477                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6478             // click in right holdings, for determining promotion piece
6479             ChessSquare p = boards[currentMove][y][x];
6480             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6481             if(p != EmptySquare) {
6482                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6483                 fromX = fromY = -1;
6484                 return;
6485             }
6486         }
6487         DrawPosition(FALSE, boards[currentMove]);
6488         return;
6489     }
6490
6491     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6492     if(clickType == Press
6493             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6494               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6495               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6496         return;
6497
6498     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6499         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6500
6501     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6502         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6503                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6504         defaultPromoChoice = DefaultPromoChoice(side);
6505     }
6506
6507     autoQueen = appData.alwaysPromoteToQueen;
6508
6509     if (fromX == -1) {
6510       int originalY = y;
6511       gatingPiece = EmptySquare;
6512       if (clickType != Press) {
6513         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6514             DragPieceEnd(xPix, yPix); dragging = 0;
6515             DrawPosition(FALSE, NULL);
6516         }
6517         return;
6518       }
6519       fromX = x; fromY = y;
6520       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6521          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6522          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6523             /* First square */
6524             if (OKToStartUserMove(fromX, fromY)) {
6525                 second = 0;
6526                 MarkTargetSquares(0);
6527                 DragPieceBegin(xPix, yPix); dragging = 1;
6528                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6529                     promoSweep = defaultPromoChoice;
6530                     selectFlag = 0; lastX = xPix; lastY = yPix;
6531                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6532                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6533                 }
6534                 if (appData.highlightDragging) {
6535                     SetHighlights(fromX, fromY, -1, -1);
6536                 }
6537             }
6538             return;
6539         }
6540     }
6541
6542     /* fromX != -1 */
6543     if (clickType == Press && gameMode != EditPosition) {
6544         ChessSquare fromP;
6545         ChessSquare toP;
6546         int frc;
6547
6548         // ignore off-board to clicks
6549         if(y < 0 || x < 0) return;
6550
6551         /* Check if clicking again on the same color piece */
6552         fromP = boards[currentMove][fromY][fromX];
6553         toP = boards[currentMove][y][x];
6554         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6555         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6556              WhitePawn <= toP && toP <= WhiteKing &&
6557              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6558              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6559             (BlackPawn <= fromP && fromP <= BlackKing &&
6560              BlackPawn <= toP && toP <= BlackKing &&
6561              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6562              !(fromP == BlackKing && toP == BlackRook && frc))) {
6563             /* Clicked again on same color piece -- changed his mind */
6564             second = (x == fromX && y == fromY);
6565             promoDefaultAltered = FALSE;
6566            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6567             if (appData.highlightDragging) {
6568                 SetHighlights(x, y, -1, -1);
6569             } else {
6570                 ClearHighlights();
6571             }
6572             if (OKToStartUserMove(x, y)) {
6573                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6574                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6575                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6576                  gatingPiece = boards[currentMove][fromY][fromX];
6577                 else gatingPiece = EmptySquare;
6578                 fromX = x;
6579                 fromY = y; dragging = 1;
6580                 MarkTargetSquares(0);
6581                 DragPieceBegin(xPix, yPix);
6582                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6583                     promoSweep = defaultPromoChoice;
6584                     selectFlag = 0; lastX = xPix; lastY = yPix;
6585                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6586                 }
6587             }
6588            }
6589            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6590            second = FALSE; 
6591         }
6592         // ignore clicks on holdings
6593         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6594     }
6595
6596     if (clickType == Release && x == fromX && y == fromY) {
6597         DragPieceEnd(xPix, yPix); dragging = 0;
6598         if (appData.animateDragging) {
6599             /* Undo animation damage if any */
6600             DrawPosition(FALSE, NULL);
6601         }
6602         if (second) {
6603             /* Second up/down in same square; just abort move */
6604             second = 0;
6605             fromX = fromY = -1;
6606             gatingPiece = EmptySquare;
6607             ClearHighlights();
6608             gotPremove = 0;
6609             ClearPremoveHighlights();
6610         } else {
6611             /* First upclick in same square; start click-click mode */
6612             SetHighlights(x, y, -1, -1);
6613         }
6614         return;
6615     }
6616
6617     /* we now have a different from- and (possibly off-board) to-square */
6618     /* Completed move */
6619     toX = x;
6620     toY = y;
6621     saveAnimate = appData.animate;
6622     if (clickType == Press) {
6623         /* Finish clickclick move */
6624         if (appData.animate || appData.highlightLastMove) {
6625             SetHighlights(fromX, fromY, toX, toY);
6626         } else {
6627             ClearHighlights();
6628         }
6629     } else {
6630         /* Finish drag move */
6631         if (appData.highlightLastMove) {
6632             SetHighlights(fromX, fromY, toX, toY);
6633         } else {
6634             ClearHighlights();
6635         }
6636         DragPieceEnd(xPix, yPix); dragging = 0;
6637         /* Don't animate move and drag both */
6638         appData.animate = FALSE;
6639     }
6640
6641     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6642     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6643         ChessSquare piece = boards[currentMove][fromY][fromX];
6644         if(gameMode == EditPosition && piece != EmptySquare &&
6645            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6646             int n;
6647
6648             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6649                 n = PieceToNumber(piece - (int)BlackPawn);
6650                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6651                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6652                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6653             } else
6654             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6655                 n = PieceToNumber(piece);
6656                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6657                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6658                 boards[currentMove][n][BOARD_WIDTH-2]++;
6659             }
6660             boards[currentMove][fromY][fromX] = EmptySquare;
6661         }
6662         ClearHighlights();
6663         fromX = fromY = -1;
6664         DrawPosition(TRUE, boards[currentMove]);
6665         return;
6666     }
6667
6668     // off-board moves should not be highlighted
6669     if(x < 0 || y < 0) ClearHighlights();
6670
6671     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6672
6673     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6674         SetHighlights(fromX, fromY, toX, toY);
6675         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6676             // [HGM] super: promotion to captured piece selected from holdings
6677             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6678             promotionChoice = TRUE;
6679             // kludge follows to temporarily execute move on display, without promoting yet
6680             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6681             boards[currentMove][toY][toX] = p;
6682             DrawPosition(FALSE, boards[currentMove]);
6683             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6684             boards[currentMove][toY][toX] = q;
6685             DisplayMessage("Click in holdings to choose piece", "");
6686             return;
6687         }
6688         PromotionPopUp();
6689     } else {
6690         int oldMove = currentMove;
6691         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6692         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6693         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6694         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6695            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6696             DrawPosition(TRUE, boards[currentMove]);
6697         fromX = fromY = -1;
6698     }
6699     appData.animate = saveAnimate;
6700     if (appData.animate || appData.animateDragging) {
6701         /* Undo animation damage if needed */
6702         DrawPosition(FALSE, NULL);
6703     }
6704 }
6705
6706 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6707 {   // front-end-free part taken out of PieceMenuPopup
6708     int whichMenu; int xSqr, ySqr;
6709
6710     if(seekGraphUp) { // [HGM] seekgraph
6711         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6712         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6713         return -2;
6714     }
6715
6716     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6717          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6718         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6719         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6720         if(action == Press)   {
6721             originalFlip = flipView;
6722             flipView = !flipView; // temporarily flip board to see game from partners perspective
6723             DrawPosition(TRUE, partnerBoard);
6724             DisplayMessage(partnerStatus, "");
6725             partnerUp = TRUE;
6726         } else if(action == Release) {
6727             flipView = originalFlip;
6728             DrawPosition(TRUE, boards[currentMove]);
6729             partnerUp = FALSE;
6730         }
6731         return -2;
6732     }
6733
6734     xSqr = EventToSquare(x, BOARD_WIDTH);
6735     ySqr = EventToSquare(y, BOARD_HEIGHT);
6736     if (action == Release) {
6737         if(pieceSweep != EmptySquare) {
6738             EditPositionMenuEvent(pieceSweep, toX, toY);
6739             pieceSweep = EmptySquare;
6740         } else UnLoadPV(); // [HGM] pv
6741     }
6742     if (action != Press) return -2; // return code to be ignored
6743     switch (gameMode) {
6744       case IcsExamining:
6745         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6746       case EditPosition:
6747         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6748         if (xSqr < 0 || ySqr < 0) return -1;
6749         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6750         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
6751         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6752         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6753         NextPiece(0);
6754         return -2;\r
6755       case IcsObserving:
6756         if(!appData.icsEngineAnalyze) return -1;
6757       case IcsPlayingWhite:
6758       case IcsPlayingBlack:
6759         if(!appData.zippyPlay) goto noZip;
6760       case AnalyzeMode:
6761       case AnalyzeFile:
6762       case MachinePlaysWhite:
6763       case MachinePlaysBlack:
6764       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6765         if (!appData.dropMenu) {
6766           LoadPV(x, y);
6767           return 2; // flag front-end to grab mouse events
6768         }
6769         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6770            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6771       case EditGame:
6772       noZip:
6773         if (xSqr < 0 || ySqr < 0) return -1;
6774         if (!appData.dropMenu || appData.testLegality &&
6775             gameInfo.variant != VariantBughouse &&
6776             gameInfo.variant != VariantCrazyhouse) return -1;
6777         whichMenu = 1; // drop menu
6778         break;
6779       default:
6780         return -1;
6781     }
6782
6783     if (((*fromX = xSqr) < 0) ||
6784         ((*fromY = ySqr) < 0)) {
6785         *fromX = *fromY = -1;
6786         return -1;
6787     }
6788     if (flipView)
6789       *fromX = BOARD_WIDTH - 1 - *fromX;
6790     else
6791       *fromY = BOARD_HEIGHT - 1 - *fromY;
6792
6793     return whichMenu;
6794 }
6795
6796 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6797 {
6798 //    char * hint = lastHint;
6799     FrontEndProgramStats stats;
6800
6801     stats.which = cps == &first ? 0 : 1;
6802     stats.depth = cpstats->depth;
6803     stats.nodes = cpstats->nodes;
6804     stats.score = cpstats->score;
6805     stats.time = cpstats->time;
6806     stats.pv = cpstats->movelist;
6807     stats.hint = lastHint;
6808     stats.an_move_index = 0;
6809     stats.an_move_count = 0;
6810
6811     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6812         stats.hint = cpstats->move_name;
6813         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6814         stats.an_move_count = cpstats->nr_moves;
6815     }
6816
6817     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
6818
6819     SetProgramStats( &stats );
6820 }
6821
6822 void
6823 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6824 {       // count all piece types
6825         int p, f, r;
6826         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6827         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6828         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6829                 p = board[r][f];
6830                 pCnt[p]++;
6831                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6832                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6833                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6834                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6835                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6836                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6837         }
6838 }
6839
6840 int
6841 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6842 {
6843         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6844         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6845
6846         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6847         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6848         if(myPawns == 2 && nMine == 3) // KPP
6849             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6850         if(myPawns == 1 && nMine == 2) // KP
6851             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6852         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6853             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6854         if(myPawns) return FALSE;
6855         if(pCnt[WhiteRook+side])
6856             return pCnt[BlackRook-side] ||
6857                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6858                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6859                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6860         if(pCnt[WhiteCannon+side]) {
6861             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6862             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6863         }
6864         if(pCnt[WhiteKnight+side])
6865             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6866         return FALSE;
6867 }
6868
6869 int
6870 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6871 {
6872         VariantClass v = gameInfo.variant;
6873
6874         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6875         if(v == VariantShatranj) return TRUE; // always winnable through baring
6876         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6877         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6878
6879         if(v == VariantXiangqi) {
6880                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6881
6882                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6883                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6884                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6885                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6886                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6887                 if(stale) // we have at least one last-rank P plus perhaps C
6888                     return majors // KPKX
6889                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6890                 else // KCA*E*
6891                     return pCnt[WhiteFerz+side] // KCAK
6892                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6893                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6894                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6895
6896         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6897                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6898
6899                 if(nMine == 1) return FALSE; // bare King
6900                 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
6901                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6902                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6903                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6904                 if(pCnt[WhiteKnight+side])
6905                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6906                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6907                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6908                 if(nBishops)
6909                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6910                 if(pCnt[WhiteAlfil+side])
6911                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6912                 if(pCnt[WhiteWazir+side])
6913                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6914         }
6915
6916         return TRUE;
6917 }
6918
6919 int
6920 Adjudicate(ChessProgramState *cps)
6921 {       // [HGM] some adjudications useful with buggy engines
6922         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6923         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6924         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6925         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6926         int k, count = 0; static int bare = 1;
6927         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6928         Boolean canAdjudicate = !appData.icsActive;
6929
6930         // most tests only when we understand the game, i.e. legality-checking on
6931             if( appData.testLegality )
6932             {   /* [HGM] Some more adjudications for obstinate engines */
6933                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6934                 static int moveCount = 6;
6935                 ChessMove result;
6936                 char *reason = NULL;
6937
6938                 /* Count what is on board. */
6939                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6940
6941                 /* Some material-based adjudications that have to be made before stalemate test */
6942                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6943                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6944                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6945                      if(canAdjudicate && appData.checkMates) {
6946                          if(engineOpponent)
6947                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6948                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6949                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6950                          return 1;
6951                      }
6952                 }
6953
6954                 /* Bare King in Shatranj (loses) or Losers (wins) */
6955                 if( nrW == 1 || nrB == 1) {
6956                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6957                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6958                      if(canAdjudicate && appData.checkMates) {
6959                          if(engineOpponent)
6960                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6961                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6962                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6963                          return 1;
6964                      }
6965                   } else
6966                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6967                   {    /* bare King */
6968                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6969                         if(canAdjudicate && appData.checkMates) {
6970                             /* but only adjudicate if adjudication enabled */
6971                             if(engineOpponent)
6972                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6973                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6974                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6975                             return 1;
6976                         }
6977                   }
6978                 } else bare = 1;
6979
6980
6981             // don't wait for engine to announce game end if we can judge ourselves
6982             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6983               case MT_CHECK:
6984                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6985                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6986                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6987                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6988                             checkCnt++;
6989                         if(checkCnt >= 2) {
6990                             reason = "Xboard adjudication: 3rd check";
6991                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6992                             break;
6993                         }
6994                     }
6995                 }
6996               case MT_NONE:
6997               default:
6998                 break;
6999               case MT_STALEMATE:
7000               case MT_STAINMATE:
7001                 reason = "Xboard adjudication: Stalemate";
7002                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7003                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7004                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7005                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7006                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7007                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7008                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7009                                                                         EP_CHECKMATE : EP_WINS);
7010                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7011                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7012                 }
7013                 break;
7014               case MT_CHECKMATE:
7015                 reason = "Xboard adjudication: Checkmate";
7016                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7017                 break;
7018             }
7019
7020                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7021                     case EP_STALEMATE:
7022                         result = GameIsDrawn; break;
7023                     case EP_CHECKMATE:
7024                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7025                     case EP_WINS:
7026                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7027                     default:
7028                         result = EndOfFile;
7029                 }
7030                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7031                     if(engineOpponent)
7032                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7033                     GameEnds( result, reason, GE_XBOARD );
7034                     return 1;
7035                 }
7036
7037                 /* Next absolutely insufficient mating material. */
7038                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7039                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7040                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7041
7042                      /* always flag draws, for judging claims */
7043                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7044
7045                      if(canAdjudicate && appData.materialDraws) {
7046                          /* but only adjudicate them if adjudication enabled */
7047                          if(engineOpponent) {
7048                            SendToProgram("force\n", engineOpponent); // suppress reply
7049                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7050                          }
7051                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7052                          return 1;
7053                      }
7054                 }
7055
7056                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7057                 if(gameInfo.variant == VariantXiangqi ?
7058                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7059                  : nrW + nrB == 4 &&
7060                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7061                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7062                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7063                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7064                    ) ) {
7065                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7066                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7067                           if(engineOpponent) {
7068                             SendToProgram("force\n", engineOpponent); // suppress reply
7069                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7070                           }
7071                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7072                           return 1;
7073                      }
7074                 } else moveCount = 6;
7075             }
7076         if (appData.debugMode) { int i;
7077             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7078                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7079                     appData.drawRepeats);
7080             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7081               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7082
7083         }
7084
7085         // Repetition draws and 50-move rule can be applied independently of legality testing
7086
7087                 /* Check for rep-draws */
7088                 count = 0;
7089                 for(k = forwardMostMove-2;
7090                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7091                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7092                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7093                     k-=2)
7094                 {   int rights=0;
7095                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7096                         /* compare castling rights */
7097                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7098                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7099                                 rights++; /* King lost rights, while rook still had them */
7100                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7101                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7102                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7103                                    rights++; /* but at least one rook lost them */
7104                         }
7105                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7106                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7107                                 rights++;
7108                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7109                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7110                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7111                                    rights++;
7112                         }
7113                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7114                             && appData.drawRepeats > 1) {
7115                              /* adjudicate after user-specified nr of repeats */
7116                              int result = GameIsDrawn;
7117                              char *details = "XBoard adjudication: repetition draw";
7118                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7119                                 // [HGM] xiangqi: check for forbidden perpetuals
7120                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7121                                 for(m=forwardMostMove; m>k; m-=2) {
7122                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7123                                         ourPerpetual = 0; // the current mover did not always check
7124                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7125                                         hisPerpetual = 0; // the opponent did not always check
7126                                 }
7127                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7128                                                                         ourPerpetual, hisPerpetual);
7129                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7130                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7131                                     details = "Xboard adjudication: perpetual checking";
7132                                 } else
7133                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7134                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7135                                 } else
7136                                 // Now check for perpetual chases
7137                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7138                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7139                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7140                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7141                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7142                                         details = "Xboard adjudication: perpetual chasing";
7143                                     } else
7144                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7145                                         break; // Abort repetition-checking loop.
7146                                 }
7147                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7148                              }
7149                              if(engineOpponent) {
7150                                SendToProgram("force\n", engineOpponent); // suppress reply
7151                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7152                              }
7153                              GameEnds( result, details, GE_XBOARD );
7154                              return 1;
7155                         }
7156                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7157                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7158                     }
7159                 }
7160
7161                 /* Now we test for 50-move draws. Determine ply count */
7162                 count = forwardMostMove;
7163                 /* look for last irreversble move */
7164                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7165                     count--;
7166                 /* if we hit starting position, add initial plies */
7167                 if( count == backwardMostMove )
7168                     count -= initialRulePlies;
7169                 count = forwardMostMove - count;
7170                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7171                         // adjust reversible move counter for checks in Xiangqi
7172                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7173                         if(i < backwardMostMove) i = backwardMostMove;
7174                         while(i <= forwardMostMove) {
7175                                 lastCheck = inCheck; // check evasion does not count
7176                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7177                                 if(inCheck || lastCheck) count--; // check does not count
7178                                 i++;
7179                         }
7180                 }
7181                 if( count >= 100)
7182                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7183                          /* this is used to judge if draw claims are legal */
7184                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7185                          if(engineOpponent) {
7186                            SendToProgram("force\n", engineOpponent); // suppress reply
7187                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7188                          }
7189                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7190                          return 1;
7191                 }
7192
7193                 /* if draw offer is pending, treat it as a draw claim
7194                  * when draw condition present, to allow engines a way to
7195                  * claim draws before making their move to avoid a race
7196                  * condition occurring after their move
7197                  */
7198                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7199                          char *p = NULL;
7200                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7201                              p = "Draw claim: 50-move rule";
7202                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7203                              p = "Draw claim: 3-fold repetition";
7204                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7205                              p = "Draw claim: insufficient mating material";
7206                          if( p != NULL && canAdjudicate) {
7207                              if(engineOpponent) {
7208                                SendToProgram("force\n", engineOpponent); // suppress reply
7209                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7210                              }
7211                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7212                              return 1;
7213                          }
7214                 }
7215
7216                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7217                     if(engineOpponent) {
7218                       SendToProgram("force\n", engineOpponent); // suppress reply
7219                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7220                     }
7221                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7222                     return 1;
7223                 }
7224         return 0;
7225 }
7226
7227 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7228 {   // [HGM] book: this routine intercepts moves to simulate book replies
7229     char *bookHit = NULL;
7230
7231     //first determine if the incoming move brings opponent into his book
7232     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7233         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7234     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7235     if(bookHit != NULL && !cps->bookSuspend) {
7236         // make sure opponent is not going to reply after receiving move to book position
7237         SendToProgram("force\n", cps);
7238         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7239     }
7240     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7241     // now arrange restart after book miss
7242     if(bookHit) {
7243         // after a book hit we never send 'go', and the code after the call to this routine
7244         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7245         char buf[MSG_SIZ];
7246         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7247         SendToProgram(buf, cps);
7248         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7249     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7250         SendToProgram("go\n", cps);
7251         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7252     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7253         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7254             SendToProgram("go\n", cps);
7255         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7256     }
7257     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7258 }
7259
7260 char *savedMessage;
7261 ChessProgramState *savedState;
7262 void DeferredBookMove(void)
7263 {
7264         if(savedState->lastPing != savedState->lastPong)
7265                     ScheduleDelayedEvent(DeferredBookMove, 10);
7266         else
7267         HandleMachineMove(savedMessage, savedState);
7268 }
7269
7270 void
7271 HandleMachineMove(message, cps)
7272      char *message;
7273      ChessProgramState *cps;
7274 {
7275     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7276     char realname[MSG_SIZ];
7277     int fromX, fromY, toX, toY;
7278     ChessMove moveType;
7279     char promoChar;
7280     char *p;
7281     int machineWhite;
7282     char *bookHit;
7283
7284     cps->userError = 0;
7285
7286 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7287     /*
7288      * Kludge to ignore BEL characters
7289      */
7290     while (*message == '\007') message++;
7291
7292     /*
7293      * [HGM] engine debug message: ignore lines starting with '#' character
7294      */
7295     if(cps->debug && *message == '#') return;
7296
7297     /*
7298      * Look for book output
7299      */
7300     if (cps == &first && bookRequested) {
7301         if (message[0] == '\t' || message[0] == ' ') {
7302             /* Part of the book output is here; append it */
7303             strcat(bookOutput, message);
7304             strcat(bookOutput, "  \n");
7305             return;
7306         } else if (bookOutput[0] != NULLCHAR) {
7307             /* All of book output has arrived; display it */
7308             char *p = bookOutput;
7309             while (*p != NULLCHAR) {
7310                 if (*p == '\t') *p = ' ';
7311                 p++;
7312             }
7313             DisplayInformation(bookOutput);
7314             bookRequested = FALSE;
7315             /* Fall through to parse the current output */
7316         }
7317     }
7318
7319     /*
7320      * Look for machine move.
7321      */
7322     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7323         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7324     {
7325         /* This method is only useful on engines that support ping */
7326         if (cps->lastPing != cps->lastPong) {
7327           if (gameMode == BeginningOfGame) {
7328             /* Extra move from before last new; ignore */
7329             if (appData.debugMode) {
7330                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7331             }
7332           } else {
7333             if (appData.debugMode) {
7334                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7335                         cps->which, gameMode);
7336             }
7337
7338             SendToProgram("undo\n", cps);
7339           }
7340           return;
7341         }
7342
7343         switch (gameMode) {
7344           case BeginningOfGame:
7345             /* Extra move from before last reset; ignore */
7346             if (appData.debugMode) {
7347                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7348             }
7349             return;
7350
7351           case EndOfGame:
7352           case IcsIdle:
7353           default:
7354             /* Extra move after we tried to stop.  The mode test is
7355                not a reliable way of detecting this problem, but it's
7356                the best we can do on engines that don't support ping.
7357             */
7358             if (appData.debugMode) {
7359                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7360                         cps->which, gameMode);
7361             }
7362             SendToProgram("undo\n", cps);
7363             return;
7364
7365           case MachinePlaysWhite:
7366           case IcsPlayingWhite:
7367             machineWhite = TRUE;
7368             break;
7369
7370           case MachinePlaysBlack:
7371           case IcsPlayingBlack:
7372             machineWhite = FALSE;
7373             break;
7374
7375           case TwoMachinesPlay:
7376             machineWhite = (cps->twoMachinesColor[0] == 'w');
7377             break;
7378         }
7379         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7380             if (appData.debugMode) {
7381                 fprintf(debugFP,
7382                         "Ignoring move out of turn by %s, gameMode %d"
7383                         ", forwardMost %d\n",
7384                         cps->which, gameMode, forwardMostMove);
7385             }
7386             return;
7387         }
7388
7389     if (appData.debugMode) { int f = forwardMostMove;
7390         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7391                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7392                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7393     }
7394         if(cps->alphaRank) AlphaRank(machineMove, 4);
7395         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7396                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7397             /* Machine move could not be parsed; ignore it. */
7398           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7399                     machineMove, _(cps->which));
7400             DisplayError(buf1, 0);
7401             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7402                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7403             if (gameMode == TwoMachinesPlay) {
7404               GameEnds(machineWhite ? BlackWins : WhiteWins,
7405                        buf1, GE_XBOARD);
7406             }
7407             return;
7408         }
7409
7410         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7411         /* So we have to redo legality test with true e.p. status here,  */
7412         /* to make sure an illegal e.p. capture does not slip through,   */
7413         /* to cause a forfeit on a justified illegal-move complaint      */
7414         /* of the opponent.                                              */
7415         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7416            ChessMove moveType;
7417            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7418                              fromY, fromX, toY, toX, promoChar);
7419             if (appData.debugMode) {
7420                 int i;
7421                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7422                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7423                 fprintf(debugFP, "castling rights\n");
7424             }
7425             if(moveType == IllegalMove) {
7426               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7427                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7428                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7429                            buf1, GE_XBOARD);
7430                 return;
7431            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7432            /* [HGM] Kludge to handle engines that send FRC-style castling
7433               when they shouldn't (like TSCP-Gothic) */
7434            switch(moveType) {
7435              case WhiteASideCastleFR:
7436              case BlackASideCastleFR:
7437                toX+=2;
7438                currentMoveString[2]++;
7439                break;
7440              case WhiteHSideCastleFR:
7441              case BlackHSideCastleFR:
7442                toX--;
7443                currentMoveString[2]--;
7444                break;
7445              default: ; // nothing to do, but suppresses warning of pedantic compilers
7446            }
7447         }
7448         hintRequested = FALSE;
7449         lastHint[0] = NULLCHAR;
7450         bookRequested = FALSE;
7451         /* Program may be pondering now */
7452         cps->maybeThinking = TRUE;
7453         if (cps->sendTime == 2) cps->sendTime = 1;
7454         if (cps->offeredDraw) cps->offeredDraw--;
7455
7456         /* [AS] Save move info*/
7457         pvInfoList[ forwardMostMove ].score = programStats.score;
7458         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7459         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7460
7461         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7462
7463         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7464         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7465             int count = 0;
7466
7467             while( count < adjudicateLossPlies ) {
7468                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7469
7470                 if( count & 1 ) {
7471                     score = -score; /* Flip score for winning side */
7472                 }
7473
7474                 if( score > adjudicateLossThreshold ) {
7475                     break;
7476                 }
7477
7478                 count++;
7479             }
7480
7481             if( count >= adjudicateLossPlies ) {
7482                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7483
7484                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7485                     "Xboard adjudication",
7486                     GE_XBOARD );
7487
7488                 return;
7489             }
7490         }
7491
7492         if(Adjudicate(cps)) {
7493             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7494             return; // [HGM] adjudicate: for all automatic game ends
7495         }
7496
7497 #if ZIPPY
7498         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7499             first.initDone) {
7500           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7501                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7502                 SendToICS("draw ");
7503                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7504           }
7505           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7506           ics_user_moved = 1;
7507           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7508                 char buf[3*MSG_SIZ];
7509
7510                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7511                         programStats.score / 100.,
7512                         programStats.depth,
7513                         programStats.time / 100.,
7514                         (unsigned int)programStats.nodes,
7515                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7516                         programStats.movelist);
7517                 SendToICS(buf);
7518 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7519           }
7520         }
7521 #endif
7522
7523         /* [AS] Clear stats for next move */
7524         ClearProgramStats();
7525         thinkOutput[0] = NULLCHAR;
7526         hiddenThinkOutputState = 0;
7527
7528         bookHit = NULL;
7529         if (gameMode == TwoMachinesPlay) {
7530             /* [HGM] relaying draw offers moved to after reception of move */
7531             /* and interpreting offer as claim if it brings draw condition */
7532             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7533                 SendToProgram("draw\n", cps->other);
7534             }
7535             if (cps->other->sendTime) {
7536                 SendTimeRemaining(cps->other,
7537                                   cps->other->twoMachinesColor[0] == 'w');
7538             }
7539             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7540             if (firstMove && !bookHit) {
7541                 firstMove = FALSE;
7542                 if (cps->other->useColors) {
7543                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7544                 }
7545                 SendToProgram("go\n", cps->other);
7546             }
7547             cps->other->maybeThinking = TRUE;
7548         }
7549
7550         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7551
7552         if (!pausing && appData.ringBellAfterMoves) {
7553             RingBell();
7554         }
7555
7556         /*
7557          * Reenable menu items that were disabled while
7558          * machine was thinking
7559          */
7560         if (gameMode != TwoMachinesPlay)
7561             SetUserThinkingEnables();
7562
7563         // [HGM] book: after book hit opponent has received move and is now in force mode
7564         // force the book reply into it, and then fake that it outputted this move by jumping
7565         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7566         if(bookHit) {
7567                 static char bookMove[MSG_SIZ]; // a bit generous?
7568
7569                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7570                 strcat(bookMove, bookHit);
7571                 message = bookMove;
7572                 cps = cps->other;
7573                 programStats.nodes = programStats.depth = programStats.time =
7574                 programStats.score = programStats.got_only_move = 0;
7575                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7576
7577                 if(cps->lastPing != cps->lastPong) {
7578                     savedMessage = message; // args for deferred call
7579                     savedState = cps;
7580                     ScheduleDelayedEvent(DeferredBookMove, 10);
7581                     return;
7582                 }
7583                 goto FakeBookMove;
7584         }
7585
7586         return;
7587     }
7588
7589     /* Set special modes for chess engines.  Later something general
7590      *  could be added here; for now there is just one kludge feature,
7591      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7592      *  when "xboard" is given as an interactive command.
7593      */
7594     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7595         cps->useSigint = FALSE;
7596         cps->useSigterm = FALSE;
7597     }
7598     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7599       ParseFeatures(message+8, cps);
7600       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7601     }
7602
7603     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7604       int dummy, s=6; char buf[MSG_SIZ];
7605       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7606       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7607       ParseFEN(boards[0], &dummy, message+s);
7608       DrawPosition(TRUE, boards[0]);
7609       startedFromSetupPosition = TRUE;
7610       return;
7611     }
7612     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7613      * want this, I was asked to put it in, and obliged.
7614      */
7615     if (!strncmp(message, "setboard ", 9)) {
7616         Board initial_position;
7617
7618         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7619
7620         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7621             DisplayError(_("Bad FEN received from engine"), 0);
7622             return ;
7623         } else {
7624            Reset(TRUE, FALSE);
7625            CopyBoard(boards[0], initial_position);
7626            initialRulePlies = FENrulePlies;
7627            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7628            else gameMode = MachinePlaysBlack;
7629            DrawPosition(FALSE, boards[currentMove]);
7630         }
7631         return;
7632     }
7633
7634     /*
7635      * Look for communication commands
7636      */
7637     if (!strncmp(message, "telluser ", 9)) {
7638         if(message[9] == '\\' && message[10] == '\\')
7639             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7640         DisplayNote(message + 9);
7641         return;
7642     }
7643     if (!strncmp(message, "tellusererror ", 14)) {
7644         cps->userError = 1;
7645         if(message[14] == '\\' && message[15] == '\\')
7646             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7647         DisplayError(message + 14, 0);
7648         return;
7649     }
7650     if (!strncmp(message, "tellopponent ", 13)) {
7651       if (appData.icsActive) {
7652         if (loggedOn) {
7653           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7654           SendToICS(buf1);
7655         }
7656       } else {
7657         DisplayNote(message + 13);
7658       }
7659       return;
7660     }
7661     if (!strncmp(message, "tellothers ", 11)) {
7662       if (appData.icsActive) {
7663         if (loggedOn) {
7664           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7665           SendToICS(buf1);
7666         }
7667       }
7668       return;
7669     }
7670     if (!strncmp(message, "tellall ", 8)) {
7671       if (appData.icsActive) {
7672         if (loggedOn) {
7673           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7674           SendToICS(buf1);
7675         }
7676       } else {
7677         DisplayNote(message + 8);
7678       }
7679       return;
7680     }
7681     if (strncmp(message, "warning", 7) == 0) {
7682         /* Undocumented feature, use tellusererror in new code */
7683         DisplayError(message, 0);
7684         return;
7685     }
7686     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7687         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7688         strcat(realname, " query");
7689         AskQuestion(realname, buf2, buf1, cps->pr);
7690         return;
7691     }
7692     /* Commands from the engine directly to ICS.  We don't allow these to be
7693      *  sent until we are logged on. Crafty kibitzes have been known to
7694      *  interfere with the login process.
7695      */
7696     if (loggedOn) {
7697         if (!strncmp(message, "tellics ", 8)) {
7698             SendToICS(message + 8);
7699             SendToICS("\n");
7700             return;
7701         }
7702         if (!strncmp(message, "tellicsnoalias ", 15)) {
7703             SendToICS(ics_prefix);
7704             SendToICS(message + 15);
7705             SendToICS("\n");
7706             return;
7707         }
7708         /* The following are for backward compatibility only */
7709         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7710             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7711             SendToICS(ics_prefix);
7712             SendToICS(message);
7713             SendToICS("\n");
7714             return;
7715         }
7716     }
7717     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7718         return;
7719     }
7720     /*
7721      * If the move is illegal, cancel it and redraw the board.
7722      * Also deal with other error cases.  Matching is rather loose
7723      * here to accommodate engines written before the spec.
7724      */
7725     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7726         strncmp(message, "Error", 5) == 0) {
7727         if (StrStr(message, "name") ||
7728             StrStr(message, "rating") || StrStr(message, "?") ||
7729             StrStr(message, "result") || StrStr(message, "board") ||
7730             StrStr(message, "bk") || StrStr(message, "computer") ||
7731             StrStr(message, "variant") || StrStr(message, "hint") ||
7732             StrStr(message, "random") || StrStr(message, "depth") ||
7733             StrStr(message, "accepted")) {
7734             return;
7735         }
7736         if (StrStr(message, "protover")) {
7737           /* Program is responding to input, so it's apparently done
7738              initializing, and this error message indicates it is
7739              protocol version 1.  So we don't need to wait any longer
7740              for it to initialize and send feature commands. */
7741           FeatureDone(cps, 1);
7742           cps->protocolVersion = 1;
7743           return;
7744         }
7745         cps->maybeThinking = FALSE;
7746
7747         if (StrStr(message, "draw")) {
7748             /* Program doesn't have "draw" command */
7749             cps->sendDrawOffers = 0;
7750             return;
7751         }
7752         if (cps->sendTime != 1 &&
7753             (StrStr(message, "time") || StrStr(message, "otim"))) {
7754           /* Program apparently doesn't have "time" or "otim" command */
7755           cps->sendTime = 0;
7756           return;
7757         }
7758         if (StrStr(message, "analyze")) {
7759             cps->analysisSupport = FALSE;
7760             cps->analyzing = FALSE;
7761             Reset(FALSE, TRUE);
7762             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7763             DisplayError(buf2, 0);
7764             return;
7765         }
7766         if (StrStr(message, "(no matching move)st")) {
7767           /* Special kludge for GNU Chess 4 only */
7768           cps->stKludge = TRUE;
7769           SendTimeControl(cps, movesPerSession, timeControl,
7770                           timeIncrement, appData.searchDepth,
7771                           searchTime);
7772           return;
7773         }
7774         if (StrStr(message, "(no matching move)sd")) {
7775           /* Special kludge for GNU Chess 4 only */
7776           cps->sdKludge = TRUE;
7777           SendTimeControl(cps, movesPerSession, timeControl,
7778                           timeIncrement, appData.searchDepth,
7779                           searchTime);
7780           return;
7781         }
7782         if (!StrStr(message, "llegal")) {
7783             return;
7784         }
7785         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7786             gameMode == IcsIdle) return;
7787         if (forwardMostMove <= backwardMostMove) return;
7788         if (pausing) PauseEvent();
7789       if(appData.forceIllegal) {
7790             // [HGM] illegal: machine refused move; force position after move into it
7791           SendToProgram("force\n", cps);
7792           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7793                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7794                 // when black is to move, while there might be nothing on a2 or black
7795                 // might already have the move. So send the board as if white has the move.
7796                 // But first we must change the stm of the engine, as it refused the last move
7797                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7798                 if(WhiteOnMove(forwardMostMove)) {
7799                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7800                     SendBoard(cps, forwardMostMove); // kludgeless board
7801                 } else {
7802                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7803                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7804                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7805                 }
7806           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7807             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7808                  gameMode == TwoMachinesPlay)
7809               SendToProgram("go\n", cps);
7810             return;
7811       } else
7812         if (gameMode == PlayFromGameFile) {
7813             /* Stop reading this game file */
7814             gameMode = EditGame;
7815             ModeHighlight();
7816         }
7817         /* [HGM] illegal-move claim should forfeit game when Xboard */
7818         /* only passes fully legal moves                            */
7819         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7820             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7821                                 "False illegal-move claim", GE_XBOARD );
7822             return; // do not take back move we tested as valid
7823         }
7824         currentMove = forwardMostMove-1;
7825         DisplayMove(currentMove-1); /* before DisplayMoveError */
7826         SwitchClocks(forwardMostMove-1); // [HGM] race
7827         DisplayBothClocks();
7828         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7829                 parseList[currentMove], _(cps->which));
7830         DisplayMoveError(buf1);
7831         DrawPosition(FALSE, boards[currentMove]);
7832         return;
7833     }
7834     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7835         /* Program has a broken "time" command that
7836            outputs a string not ending in newline.
7837            Don't use it. */
7838         cps->sendTime = 0;
7839     }
7840
7841     /*
7842      * If chess program startup fails, exit with an error message.
7843      * Attempts to recover here are futile.
7844      */
7845     if ((StrStr(message, "unknown host") != NULL)
7846         || (StrStr(message, "No remote directory") != NULL)
7847         || (StrStr(message, "not found") != NULL)
7848         || (StrStr(message, "No such file") != NULL)
7849         || (StrStr(message, "can't alloc") != NULL)
7850         || (StrStr(message, "Permission denied") != NULL)) {
7851
7852         cps->maybeThinking = FALSE;
7853         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7854                 _(cps->which), cps->program, cps->host, message);
7855         RemoveInputSource(cps->isr);
7856         DisplayFatalError(buf1, 0, 1);
7857         return;
7858     }
7859
7860     /*
7861      * Look for hint output
7862      */
7863     if (sscanf(message, "Hint: %s", buf1) == 1) {
7864         if (cps == &first && hintRequested) {
7865             hintRequested = FALSE;
7866             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7867                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7868                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7869                                     PosFlags(forwardMostMove),
7870                                     fromY, fromX, toY, toX, promoChar, buf1);
7871                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7872                 DisplayInformation(buf2);
7873             } else {
7874                 /* Hint move could not be parsed!? */
7875               snprintf(buf2, sizeof(buf2),
7876                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7877                         buf1, _(cps->which));
7878                 DisplayError(buf2, 0);
7879             }
7880         } else {
7881           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7882         }
7883         return;
7884     }
7885
7886     /*
7887      * Ignore other messages if game is not in progress
7888      */
7889     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7890         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7891
7892     /*
7893      * look for win, lose, draw, or draw offer
7894      */
7895     if (strncmp(message, "1-0", 3) == 0) {
7896         char *p, *q, *r = "";
7897         p = strchr(message, '{');
7898         if (p) {
7899             q = strchr(p, '}');
7900             if (q) {
7901                 *q = NULLCHAR;
7902                 r = p + 1;
7903             }
7904         }
7905         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7906         return;
7907     } else if (strncmp(message, "0-1", 3) == 0) {
7908         char *p, *q, *r = "";
7909         p = strchr(message, '{');
7910         if (p) {
7911             q = strchr(p, '}');
7912             if (q) {
7913                 *q = NULLCHAR;
7914                 r = p + 1;
7915             }
7916         }
7917         /* Kludge for Arasan 4.1 bug */
7918         if (strcmp(r, "Black resigns") == 0) {
7919             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7920             return;
7921         }
7922         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7923         return;
7924     } else if (strncmp(message, "1/2", 3) == 0) {
7925         char *p, *q, *r = "";
7926         p = strchr(message, '{');
7927         if (p) {
7928             q = strchr(p, '}');
7929             if (q) {
7930                 *q = NULLCHAR;
7931                 r = p + 1;
7932             }
7933         }
7934
7935         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7936         return;
7937
7938     } else if (strncmp(message, "White resign", 12) == 0) {
7939         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7940         return;
7941     } else if (strncmp(message, "Black resign", 12) == 0) {
7942         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7943         return;
7944     } else if (strncmp(message, "White matches", 13) == 0 ||
7945                strncmp(message, "Black matches", 13) == 0   ) {
7946         /* [HGM] ignore GNUShogi noises */
7947         return;
7948     } else if (strncmp(message, "White", 5) == 0 &&
7949                message[5] != '(' &&
7950                StrStr(message, "Black") == NULL) {
7951         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7952         return;
7953     } else if (strncmp(message, "Black", 5) == 0 &&
7954                message[5] != '(') {
7955         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7956         return;
7957     } else if (strcmp(message, "resign") == 0 ||
7958                strcmp(message, "computer resigns") == 0) {
7959         switch (gameMode) {
7960           case MachinePlaysBlack:
7961           case IcsPlayingBlack:
7962             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7963             break;
7964           case MachinePlaysWhite:
7965           case IcsPlayingWhite:
7966             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7967             break;
7968           case TwoMachinesPlay:
7969             if (cps->twoMachinesColor[0] == 'w')
7970               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7971             else
7972               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7973             break;
7974           default:
7975             /* can't happen */
7976             break;
7977         }
7978         return;
7979     } else if (strncmp(message, "opponent mates", 14) == 0) {
7980         switch (gameMode) {
7981           case MachinePlaysBlack:
7982           case IcsPlayingBlack:
7983             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7984             break;
7985           case MachinePlaysWhite:
7986           case IcsPlayingWhite:
7987             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7988             break;
7989           case TwoMachinesPlay:
7990             if (cps->twoMachinesColor[0] == 'w')
7991               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7992             else
7993               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7994             break;
7995           default:
7996             /* can't happen */
7997             break;
7998         }
7999         return;
8000     } else if (strncmp(message, "computer mates", 14) == 0) {
8001         switch (gameMode) {
8002           case MachinePlaysBlack:
8003           case IcsPlayingBlack:
8004             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8005             break;
8006           case MachinePlaysWhite:
8007           case IcsPlayingWhite:
8008             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8009             break;
8010           case TwoMachinesPlay:
8011             if (cps->twoMachinesColor[0] == 'w')
8012               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8013             else
8014               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8015             break;
8016           default:
8017             /* can't happen */
8018             break;
8019         }
8020         return;
8021     } else if (strncmp(message, "checkmate", 9) == 0) {
8022         if (WhiteOnMove(forwardMostMove)) {
8023             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8024         } else {
8025             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8026         }
8027         return;
8028     } else if (strstr(message, "Draw") != NULL ||
8029                strstr(message, "game is a draw") != NULL) {
8030         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8031         return;
8032     } else if (strstr(message, "offer") != NULL &&
8033                strstr(message, "draw") != NULL) {
8034 #if ZIPPY
8035         if (appData.zippyPlay && first.initDone) {
8036             /* Relay offer to ICS */
8037             SendToICS(ics_prefix);
8038             SendToICS("draw\n");
8039         }
8040 #endif
8041         cps->offeredDraw = 2; /* valid until this engine moves twice */
8042         if (gameMode == TwoMachinesPlay) {
8043             if (cps->other->offeredDraw) {
8044                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8045             /* [HGM] in two-machine mode we delay relaying draw offer      */
8046             /* until after we also have move, to see if it is really claim */
8047             }
8048         } else if (gameMode == MachinePlaysWhite ||
8049                    gameMode == MachinePlaysBlack) {
8050           if (userOfferedDraw) {
8051             DisplayInformation(_("Machine accepts your draw offer"));
8052             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8053           } else {
8054             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8055           }
8056         }
8057     }
8058
8059
8060     /*
8061      * Look for thinking output
8062      */
8063     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8064           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8065                                 ) {
8066         int plylev, mvleft, mvtot, curscore, time;
8067         char mvname[MOVE_LEN];
8068         u64 nodes; // [DM]
8069         char plyext;
8070         int ignore = FALSE;
8071         int prefixHint = FALSE;
8072         mvname[0] = NULLCHAR;
8073
8074         switch (gameMode) {
8075           case MachinePlaysBlack:
8076           case IcsPlayingBlack:
8077             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8078             break;
8079           case MachinePlaysWhite:
8080           case IcsPlayingWhite:
8081             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8082             break;
8083           case AnalyzeMode:
8084           case AnalyzeFile:
8085             break;
8086           case IcsObserving: /* [DM] icsEngineAnalyze */
8087             if (!appData.icsEngineAnalyze) ignore = TRUE;
8088             break;
8089           case TwoMachinesPlay:
8090             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8091                 ignore = TRUE;
8092             }
8093             break;
8094           default:
8095             ignore = TRUE;
8096             break;
8097         }
8098
8099         if (!ignore) {
8100             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8101             buf1[0] = NULLCHAR;
8102             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8103                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8104
8105                 if (plyext != ' ' && plyext != '\t') {
8106                     time *= 100;
8107                 }
8108
8109                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8110                 if( cps->scoreIsAbsolute &&
8111                     ( gameMode == MachinePlaysBlack ||
8112                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8113                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8114                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8115                      !WhiteOnMove(currentMove)
8116                     ) )
8117                 {
8118                     curscore = -curscore;
8119                 }
8120
8121
8122                 tempStats.depth = plylev;
8123                 tempStats.nodes = nodes;
8124                 tempStats.time = time;
8125                 tempStats.score = curscore;
8126                 tempStats.got_only_move = 0;
8127
8128                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8129                         int ticklen;
8130
8131                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8132                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8133                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8134                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8135                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8136                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8137                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8138                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8139                 }
8140
8141                 /* Buffer overflow protection */
8142                 if (buf1[0] != NULLCHAR) {
8143                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8144                         && appData.debugMode) {
8145                         fprintf(debugFP,
8146                                 "PV is too long; using the first %u bytes.\n",
8147                                 (unsigned) sizeof(tempStats.movelist) - 1);
8148                     }
8149
8150                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8151                 } else {
8152                     sprintf(tempStats.movelist, " no PV\n");
8153                 }
8154
8155                 if (tempStats.seen_stat) {
8156                     tempStats.ok_to_send = 1;
8157                 }
8158
8159                 if (strchr(tempStats.movelist, '(') != NULL) {
8160                     tempStats.line_is_book = 1;
8161                     tempStats.nr_moves = 0;
8162                     tempStats.moves_left = 0;
8163                 } else {
8164                     tempStats.line_is_book = 0;
8165                 }
8166
8167                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8168                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8169
8170                 SendProgramStatsToFrontend( cps, &tempStats );
8171
8172                 /*
8173                     [AS] Protect the thinkOutput buffer from overflow... this
8174                     is only useful if buf1 hasn't overflowed first!
8175                 */
8176                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8177                          plylev,
8178                          (gameMode == TwoMachinesPlay ?
8179                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8180                          ((double) curscore) / 100.0,
8181                          prefixHint ? lastHint : "",
8182                          prefixHint ? " " : "" );
8183
8184                 if( buf1[0] != NULLCHAR ) {
8185                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8186
8187                     if( strlen(buf1) > max_len ) {
8188                         if( appData.debugMode) {
8189                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8190                         }
8191                         buf1[max_len+1] = '\0';
8192                     }
8193
8194                     strcat( thinkOutput, buf1 );
8195                 }
8196
8197                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8198                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8199                     DisplayMove(currentMove - 1);
8200                 }
8201                 return;
8202
8203             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8204                 /* crafty (9.25+) says "(only move) <move>"
8205                  * if there is only 1 legal move
8206                  */
8207                 sscanf(p, "(only move) %s", buf1);
8208                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8209                 sprintf(programStats.movelist, "%s (only move)", buf1);
8210                 programStats.depth = 1;
8211                 programStats.nr_moves = 1;
8212                 programStats.moves_left = 1;
8213                 programStats.nodes = 1;
8214                 programStats.time = 1;
8215                 programStats.got_only_move = 1;
8216
8217                 /* Not really, but we also use this member to
8218                    mean "line isn't going to change" (Crafty
8219                    isn't searching, so stats won't change) */
8220                 programStats.line_is_book = 1;
8221
8222                 SendProgramStatsToFrontend( cps, &programStats );
8223
8224                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8225                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8226                     DisplayMove(currentMove - 1);
8227                 }
8228                 return;
8229             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8230                               &time, &nodes, &plylev, &mvleft,
8231                               &mvtot, mvname) >= 5) {
8232                 /* The stat01: line is from Crafty (9.29+) in response
8233                    to the "." command */
8234                 programStats.seen_stat = 1;
8235                 cps->maybeThinking = TRUE;
8236
8237                 if (programStats.got_only_move || !appData.periodicUpdates)
8238                   return;
8239
8240                 programStats.depth = plylev;
8241                 programStats.time = time;
8242                 programStats.nodes = nodes;
8243                 programStats.moves_left = mvleft;
8244                 programStats.nr_moves = mvtot;
8245                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8246                 programStats.ok_to_send = 1;
8247                 programStats.movelist[0] = '\0';
8248
8249                 SendProgramStatsToFrontend( cps, &programStats );
8250
8251                 return;
8252
8253             } else if (strncmp(message,"++",2) == 0) {
8254                 /* Crafty 9.29+ outputs this */
8255                 programStats.got_fail = 2;
8256                 return;
8257
8258             } else if (strncmp(message,"--",2) == 0) {
8259                 /* Crafty 9.29+ outputs this */
8260                 programStats.got_fail = 1;
8261                 return;
8262
8263             } else if (thinkOutput[0] != NULLCHAR &&
8264                        strncmp(message, "    ", 4) == 0) {
8265                 unsigned message_len;
8266
8267                 p = message;
8268                 while (*p && *p == ' ') p++;
8269
8270                 message_len = strlen( p );
8271
8272                 /* [AS] Avoid buffer overflow */
8273                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8274                     strcat(thinkOutput, " ");
8275                     strcat(thinkOutput, p);
8276                 }
8277
8278                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8279                     strcat(programStats.movelist, " ");
8280                     strcat(programStats.movelist, p);
8281                 }
8282
8283                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8284                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8285                     DisplayMove(currentMove - 1);
8286                 }
8287                 return;
8288             }
8289         }
8290         else {
8291             buf1[0] = NULLCHAR;
8292
8293             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8294                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8295             {
8296                 ChessProgramStats cpstats;
8297
8298                 if (plyext != ' ' && plyext != '\t') {
8299                     time *= 100;
8300                 }
8301
8302                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8303                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8304                     curscore = -curscore;
8305                 }
8306
8307                 cpstats.depth = plylev;
8308                 cpstats.nodes = nodes;
8309                 cpstats.time = time;
8310                 cpstats.score = curscore;
8311                 cpstats.got_only_move = 0;
8312                 cpstats.movelist[0] = '\0';
8313
8314                 if (buf1[0] != NULLCHAR) {
8315                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8316                 }
8317
8318                 cpstats.ok_to_send = 0;
8319                 cpstats.line_is_book = 0;
8320                 cpstats.nr_moves = 0;
8321                 cpstats.moves_left = 0;
8322
8323                 SendProgramStatsToFrontend( cps, &cpstats );
8324             }
8325         }
8326     }
8327 }
8328
8329
8330 /* Parse a game score from the character string "game", and
8331    record it as the history of the current game.  The game
8332    score is NOT assumed to start from the standard position.
8333    The display is not updated in any way.
8334    */
8335 void
8336 ParseGameHistory(game)
8337      char *game;
8338 {
8339     ChessMove moveType;
8340     int fromX, fromY, toX, toY, boardIndex;
8341     char promoChar;
8342     char *p, *q;
8343     char buf[MSG_SIZ];
8344
8345     if (appData.debugMode)
8346       fprintf(debugFP, "Parsing game history: %s\n", game);
8347
8348     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8349     gameInfo.site = StrSave(appData.icsHost);
8350     gameInfo.date = PGNDate();
8351     gameInfo.round = StrSave("-");
8352
8353     /* Parse out names of players */
8354     while (*game == ' ') game++;
8355     p = buf;
8356     while (*game != ' ') *p++ = *game++;
8357     *p = NULLCHAR;
8358     gameInfo.white = StrSave(buf);
8359     while (*game == ' ') game++;
8360     p = buf;
8361     while (*game != ' ' && *game != '\n') *p++ = *game++;
8362     *p = NULLCHAR;
8363     gameInfo.black = StrSave(buf);
8364
8365     /* Parse moves */
8366     boardIndex = blackPlaysFirst ? 1 : 0;
8367     yynewstr(game);
8368     for (;;) {
8369         yyboardindex = boardIndex;
8370         moveType = (ChessMove) Myylex();
8371         switch (moveType) {
8372           case IllegalMove:             /* maybe suicide chess, etc. */
8373   if (appData.debugMode) {
8374     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8375     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8376     setbuf(debugFP, NULL);
8377   }
8378           case WhitePromotion:
8379           case BlackPromotion:
8380           case WhiteNonPromotion:
8381           case BlackNonPromotion:
8382           case NormalMove:
8383           case WhiteCapturesEnPassant:
8384           case BlackCapturesEnPassant:
8385           case WhiteKingSideCastle:
8386           case WhiteQueenSideCastle:
8387           case BlackKingSideCastle:
8388           case BlackQueenSideCastle:
8389           case WhiteKingSideCastleWild:
8390           case WhiteQueenSideCastleWild:
8391           case BlackKingSideCastleWild:
8392           case BlackQueenSideCastleWild:
8393           /* PUSH Fabien */
8394           case WhiteHSideCastleFR:
8395           case WhiteASideCastleFR:
8396           case BlackHSideCastleFR:
8397           case BlackASideCastleFR:
8398           /* POP Fabien */
8399             fromX = currentMoveString[0] - AAA;
8400             fromY = currentMoveString[1] - ONE;
8401             toX = currentMoveString[2] - AAA;
8402             toY = currentMoveString[3] - ONE;
8403             promoChar = currentMoveString[4];
8404             break;
8405           case WhiteDrop:
8406           case BlackDrop:
8407             fromX = moveType == WhiteDrop ?
8408               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8409             (int) CharToPiece(ToLower(currentMoveString[0]));
8410             fromY = DROP_RANK;
8411             toX = currentMoveString[2] - AAA;
8412             toY = currentMoveString[3] - ONE;
8413             promoChar = NULLCHAR;
8414             break;
8415           case AmbiguousMove:
8416             /* bug? */
8417             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8418   if (appData.debugMode) {
8419     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8420     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8421     setbuf(debugFP, NULL);
8422   }
8423             DisplayError(buf, 0);
8424             return;
8425           case ImpossibleMove:
8426             /* bug? */
8427             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8428   if (appData.debugMode) {
8429     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8430     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8431     setbuf(debugFP, NULL);
8432   }
8433             DisplayError(buf, 0);
8434             return;
8435           case EndOfFile:
8436             if (boardIndex < backwardMostMove) {
8437                 /* Oops, gap.  How did that happen? */
8438                 DisplayError(_("Gap in move list"), 0);
8439                 return;
8440             }
8441             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8442             if (boardIndex > forwardMostMove) {
8443                 forwardMostMove = boardIndex;
8444             }
8445             return;
8446           case ElapsedTime:
8447             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8448                 strcat(parseList[boardIndex-1], " ");
8449                 strcat(parseList[boardIndex-1], yy_text);
8450             }
8451             continue;
8452           case Comment:
8453           case PGNTag:
8454           case NAG:
8455           default:
8456             /* ignore */
8457             continue;
8458           case WhiteWins:
8459           case BlackWins:
8460           case GameIsDrawn:
8461           case GameUnfinished:
8462             if (gameMode == IcsExamining) {
8463                 if (boardIndex < backwardMostMove) {
8464                     /* Oops, gap.  How did that happen? */
8465                     return;
8466                 }
8467                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8468                 return;
8469             }
8470             gameInfo.result = moveType;
8471             p = strchr(yy_text, '{');
8472             if (p == NULL) p = strchr(yy_text, '(');
8473             if (p == NULL) {
8474                 p = yy_text;
8475                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8476             } else {
8477                 q = strchr(p, *p == '{' ? '}' : ')');
8478                 if (q != NULL) *q = NULLCHAR;
8479                 p++;
8480             }
8481             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8482             gameInfo.resultDetails = StrSave(p);
8483             continue;
8484         }
8485         if (boardIndex >= forwardMostMove &&
8486             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8487             backwardMostMove = blackPlaysFirst ? 1 : 0;
8488             return;
8489         }
8490         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8491                                  fromY, fromX, toY, toX, promoChar,
8492                                  parseList[boardIndex]);
8493         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8494         /* currentMoveString is set as a side-effect of yylex */
8495         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8496         strcat(moveList[boardIndex], "\n");
8497         boardIndex++;
8498         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8499         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8500           case MT_NONE:
8501           case MT_STALEMATE:
8502           default:
8503             break;
8504           case MT_CHECK:
8505             if(gameInfo.variant != VariantShogi)
8506                 strcat(parseList[boardIndex - 1], "+");
8507             break;
8508           case MT_CHECKMATE:
8509           case MT_STAINMATE:
8510             strcat(parseList[boardIndex - 1], "#");
8511             break;
8512         }
8513     }
8514 }
8515
8516
8517 /* Apply a move to the given board  */
8518 void
8519 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8520      int fromX, fromY, toX, toY;
8521      int promoChar;
8522      Board board;
8523 {
8524   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8525   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8526
8527     /* [HGM] compute & store e.p. status and castling rights for new position */
8528     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8529
8530       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8531       oldEP = (signed char)board[EP_STATUS];
8532       board[EP_STATUS] = EP_NONE;
8533
8534       if( board[toY][toX] != EmptySquare )
8535            board[EP_STATUS] = EP_CAPTURE;
8536
8537   if (fromY == DROP_RANK) {
8538         /* must be first */
8539         piece = board[toY][toX] = (ChessSquare) fromX;
8540   } else {
8541       int i;
8542
8543       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8544            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8545                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8546       } else
8547       if( board[fromY][fromX] == WhitePawn ) {
8548            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8549                board[EP_STATUS] = EP_PAWN_MOVE;
8550            if( toY-fromY==2) {
8551                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8552                         gameInfo.variant != VariantBerolina || toX < fromX)
8553                       board[EP_STATUS] = toX | berolina;
8554                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8555                         gameInfo.variant != VariantBerolina || toX > fromX)
8556                       board[EP_STATUS] = toX;
8557            }
8558       } else
8559       if( board[fromY][fromX] == BlackPawn ) {
8560            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8561                board[EP_STATUS] = EP_PAWN_MOVE;
8562            if( toY-fromY== -2) {
8563                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8564                         gameInfo.variant != VariantBerolina || toX < fromX)
8565                       board[EP_STATUS] = toX | berolina;
8566                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8567                         gameInfo.variant != VariantBerolina || toX > fromX)
8568                       board[EP_STATUS] = toX;
8569            }
8570        }
8571
8572        for(i=0; i<nrCastlingRights; i++) {
8573            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8574               board[CASTLING][i] == toX   && castlingRank[i] == toY
8575              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8576        }
8577
8578      if (fromX == toX && fromY == toY) return;
8579
8580      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8581      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8582      if(gameInfo.variant == VariantKnightmate)
8583          king += (int) WhiteUnicorn - (int) WhiteKing;
8584
8585     /* Code added by Tord: */
8586     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8587     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8588         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8589       board[fromY][fromX] = EmptySquare;
8590       board[toY][toX] = EmptySquare;
8591       if((toX > fromX) != (piece == WhiteRook)) {
8592         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8593       } else {
8594         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8595       }
8596     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8597                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8598       board[fromY][fromX] = EmptySquare;
8599       board[toY][toX] = EmptySquare;
8600       if((toX > fromX) != (piece == BlackRook)) {
8601         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8602       } else {
8603         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8604       }
8605     /* End of code added by Tord */
8606
8607     } else if (board[fromY][fromX] == king
8608         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8609         && toY == fromY && toX > fromX+1) {
8610         board[fromY][fromX] = EmptySquare;
8611         board[toY][toX] = king;
8612         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8613         board[fromY][BOARD_RGHT-1] = EmptySquare;
8614     } else if (board[fromY][fromX] == king
8615         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8616                && toY == fromY && toX < fromX-1) {
8617         board[fromY][fromX] = EmptySquare;
8618         board[toY][toX] = king;
8619         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8620         board[fromY][BOARD_LEFT] = EmptySquare;
8621     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8622                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8623                && toY >= BOARD_HEIGHT-promoRank
8624                ) {
8625         /* white pawn promotion */
8626         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8627         if (board[toY][toX] == EmptySquare) {
8628             board[toY][toX] = WhiteQueen;
8629         }
8630         if(gameInfo.variant==VariantBughouse ||
8631            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8632             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8633         board[fromY][fromX] = EmptySquare;
8634     } else if ((fromY == BOARD_HEIGHT-4)
8635                && (toX != fromX)
8636                && gameInfo.variant != VariantXiangqi
8637                && gameInfo.variant != VariantBerolina
8638                && (board[fromY][fromX] == WhitePawn)
8639                && (board[toY][toX] == EmptySquare)) {
8640         board[fromY][fromX] = EmptySquare;
8641         board[toY][toX] = WhitePawn;
8642         captured = board[toY - 1][toX];
8643         board[toY - 1][toX] = EmptySquare;
8644     } else if ((fromY == BOARD_HEIGHT-4)
8645                && (toX == fromX)
8646                && gameInfo.variant == VariantBerolina
8647                && (board[fromY][fromX] == WhitePawn)
8648                && (board[toY][toX] == EmptySquare)) {
8649         board[fromY][fromX] = EmptySquare;
8650         board[toY][toX] = WhitePawn;
8651         if(oldEP & EP_BEROLIN_A) {
8652                 captured = board[fromY][fromX-1];
8653                 board[fromY][fromX-1] = EmptySquare;
8654         }else{  captured = board[fromY][fromX+1];
8655                 board[fromY][fromX+1] = EmptySquare;
8656         }
8657     } else if (board[fromY][fromX] == king
8658         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8659                && toY == fromY && toX > fromX+1) {
8660         board[fromY][fromX] = EmptySquare;
8661         board[toY][toX] = king;
8662         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8663         board[fromY][BOARD_RGHT-1] = EmptySquare;
8664     } else if (board[fromY][fromX] == king
8665         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8666                && toY == fromY && toX < fromX-1) {
8667         board[fromY][fromX] = EmptySquare;
8668         board[toY][toX] = king;
8669         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8670         board[fromY][BOARD_LEFT] = EmptySquare;
8671     } else if (fromY == 7 && fromX == 3
8672                && board[fromY][fromX] == BlackKing
8673                && toY == 7 && toX == 5) {
8674         board[fromY][fromX] = EmptySquare;
8675         board[toY][toX] = BlackKing;
8676         board[fromY][7] = EmptySquare;
8677         board[toY][4] = BlackRook;
8678     } else if (fromY == 7 && fromX == 3
8679                && board[fromY][fromX] == BlackKing
8680                && toY == 7 && toX == 1) {
8681         board[fromY][fromX] = EmptySquare;
8682         board[toY][toX] = BlackKing;
8683         board[fromY][0] = EmptySquare;
8684         board[toY][2] = BlackRook;
8685     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8686                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8687                && toY < promoRank
8688                ) {
8689         /* black pawn promotion */
8690         board[toY][toX] = CharToPiece(ToLower(promoChar));
8691         if (board[toY][toX] == EmptySquare) {
8692             board[toY][toX] = BlackQueen;
8693         }
8694         if(gameInfo.variant==VariantBughouse ||
8695            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8696             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8697         board[fromY][fromX] = EmptySquare;
8698     } else if ((fromY == 3)
8699                && (toX != fromX)
8700                && gameInfo.variant != VariantXiangqi
8701                && gameInfo.variant != VariantBerolina
8702                && (board[fromY][fromX] == BlackPawn)
8703                && (board[toY][toX] == EmptySquare)) {
8704         board[fromY][fromX] = EmptySquare;
8705         board[toY][toX] = BlackPawn;
8706         captured = board[toY + 1][toX];
8707         board[toY + 1][toX] = EmptySquare;
8708     } else if ((fromY == 3)
8709                && (toX == fromX)
8710                && gameInfo.variant == VariantBerolina
8711                && (board[fromY][fromX] == BlackPawn)
8712                && (board[toY][toX] == EmptySquare)) {
8713         board[fromY][fromX] = EmptySquare;
8714         board[toY][toX] = BlackPawn;
8715         if(oldEP & EP_BEROLIN_A) {
8716                 captured = board[fromY][fromX-1];
8717                 board[fromY][fromX-1] = EmptySquare;
8718         }else{  captured = board[fromY][fromX+1];
8719                 board[fromY][fromX+1] = EmptySquare;
8720         }
8721     } else {
8722         board[toY][toX] = board[fromY][fromX];
8723         board[fromY][fromX] = EmptySquare;
8724     }
8725   }
8726
8727     if (gameInfo.holdingsWidth != 0) {
8728
8729       /* !!A lot more code needs to be written to support holdings  */
8730       /* [HGM] OK, so I have written it. Holdings are stored in the */
8731       /* penultimate board files, so they are automaticlly stored   */
8732       /* in the game history.                                       */
8733       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8734                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8735         /* Delete from holdings, by decreasing count */
8736         /* and erasing image if necessary            */
8737         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8738         if(p < (int) BlackPawn) { /* white drop */
8739              p -= (int)WhitePawn;
8740                  p = PieceToNumber((ChessSquare)p);
8741              if(p >= gameInfo.holdingsSize) p = 0;
8742              if(--board[p][BOARD_WIDTH-2] <= 0)
8743                   board[p][BOARD_WIDTH-1] = EmptySquare;
8744              if((int)board[p][BOARD_WIDTH-2] < 0)
8745                         board[p][BOARD_WIDTH-2] = 0;
8746         } else {                  /* black drop */
8747              p -= (int)BlackPawn;
8748                  p = PieceToNumber((ChessSquare)p);
8749              if(p >= gameInfo.holdingsSize) p = 0;
8750              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8751                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8752              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8753                         board[BOARD_HEIGHT-1-p][1] = 0;
8754         }
8755       }
8756       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8757           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8758         /* [HGM] holdings: Add to holdings, if holdings exist */
8759         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8760                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8761                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8762         }
8763         p = (int) captured;
8764         if (p >= (int) BlackPawn) {
8765           p -= (int)BlackPawn;
8766           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8767                   /* in Shogi restore piece to its original  first */
8768                   captured = (ChessSquare) (DEMOTED captured);
8769                   p = DEMOTED p;
8770           }
8771           p = PieceToNumber((ChessSquare)p);
8772           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8773           board[p][BOARD_WIDTH-2]++;
8774           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8775         } else {
8776           p -= (int)WhitePawn;
8777           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8778                   captured = (ChessSquare) (DEMOTED captured);
8779                   p = DEMOTED p;
8780           }
8781           p = PieceToNumber((ChessSquare)p);
8782           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8783           board[BOARD_HEIGHT-1-p][1]++;
8784           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8785         }
8786       }
8787     } else if (gameInfo.variant == VariantAtomic) {
8788       if (captured != EmptySquare) {
8789         int y, x;
8790         for (y = toY-1; y <= toY+1; y++) {
8791           for (x = toX-1; x <= toX+1; x++) {
8792             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8793                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8794               board[y][x] = EmptySquare;
8795             }
8796           }
8797         }
8798         board[toY][toX] = EmptySquare;
8799       }
8800     }
8801     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8802         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8803     } else
8804     if(promoChar == '+') {
8805         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8806         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8807     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8808         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8809     }
8810     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8811                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8812         // [HGM] superchess: take promotion piece out of holdings
8813         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8814         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8815             if(!--board[k][BOARD_WIDTH-2])
8816                 board[k][BOARD_WIDTH-1] = EmptySquare;
8817         } else {
8818             if(!--board[BOARD_HEIGHT-1-k][1])
8819                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8820         }
8821     }
8822
8823 }
8824
8825 /* Updates forwardMostMove */
8826 void
8827 MakeMove(fromX, fromY, toX, toY, promoChar)
8828      int fromX, fromY, toX, toY;
8829      int promoChar;
8830 {
8831 //    forwardMostMove++; // [HGM] bare: moved downstream
8832
8833     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8834         int timeLeft; static int lastLoadFlag=0; int king, piece;
8835         piece = boards[forwardMostMove][fromY][fromX];
8836         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8837         if(gameInfo.variant == VariantKnightmate)
8838             king += (int) WhiteUnicorn - (int) WhiteKing;
8839         if(forwardMostMove == 0) {
8840             if(blackPlaysFirst)
8841                 fprintf(serverMoves, "%s;", second.tidy);
8842             fprintf(serverMoves, "%s;", first.tidy);
8843             if(!blackPlaysFirst)
8844                 fprintf(serverMoves, "%s;", second.tidy);
8845         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8846         lastLoadFlag = loadFlag;
8847         // print base move
8848         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8849         // print castling suffix
8850         if( toY == fromY && piece == king ) {
8851             if(toX-fromX > 1)
8852                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8853             if(fromX-toX >1)
8854                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8855         }
8856         // e.p. suffix
8857         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8858              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8859              boards[forwardMostMove][toY][toX] == EmptySquare
8860              && fromX != toX && fromY != toY)
8861                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8862         // promotion suffix
8863         if(promoChar != NULLCHAR)
8864                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8865         if(!loadFlag) {
8866             fprintf(serverMoves, "/%d/%d",
8867                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8868             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8869             else                      timeLeft = blackTimeRemaining/1000;
8870             fprintf(serverMoves, "/%d", timeLeft);
8871         }
8872         fflush(serverMoves);
8873     }
8874
8875     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8876       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8877                         0, 1);
8878       return;
8879     }
8880     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8881     if (commentList[forwardMostMove+1] != NULL) {
8882         free(commentList[forwardMostMove+1]);
8883         commentList[forwardMostMove+1] = NULL;
8884     }
8885     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8886     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8887     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8888     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8889     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8890     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8891     gameInfo.result = GameUnfinished;
8892     if (gameInfo.resultDetails != NULL) {
8893         free(gameInfo.resultDetails);
8894         gameInfo.resultDetails = NULL;
8895     }
8896     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8897                               moveList[forwardMostMove - 1]);
8898     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8899                              PosFlags(forwardMostMove - 1),
8900                              fromY, fromX, toY, toX, promoChar,
8901                              parseList[forwardMostMove - 1]);
8902     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8903       case MT_NONE:
8904       case MT_STALEMATE:
8905       default:
8906         break;
8907       case MT_CHECK:
8908         if(gameInfo.variant != VariantShogi)
8909             strcat(parseList[forwardMostMove - 1], "+");
8910         break;
8911       case MT_CHECKMATE:
8912       case MT_STAINMATE:
8913         strcat(parseList[forwardMostMove - 1], "#");
8914         break;
8915     }
8916     if (appData.debugMode) {
8917         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8918     }
8919
8920 }
8921
8922 /* Updates currentMove if not pausing */
8923 void
8924 ShowMove(fromX, fromY, toX, toY)
8925 {
8926     int instant = (gameMode == PlayFromGameFile) ?
8927         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8928     if(appData.noGUI) return;
8929     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8930         if (!instant) {
8931             if (forwardMostMove == currentMove + 1) {
8932                 AnimateMove(boards[forwardMostMove - 1],
8933                             fromX, fromY, toX, toY);
8934             }
8935             if (appData.highlightLastMove) {
8936                 SetHighlights(fromX, fromY, toX, toY);
8937             }
8938         }
8939         currentMove = forwardMostMove;
8940     }
8941
8942     if (instant) return;
8943
8944     DisplayMove(currentMove - 1);
8945     DrawPosition(FALSE, boards[currentMove]);
8946     DisplayBothClocks();
8947     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8948 }
8949
8950 void SendEgtPath(ChessProgramState *cps)
8951 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8952         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8953
8954         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8955
8956         while(*p) {
8957             char c, *q = name+1, *r, *s;
8958
8959             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8960             while(*p && *p != ',') *q++ = *p++;
8961             *q++ = ':'; *q = 0;
8962             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8963                 strcmp(name, ",nalimov:") == 0 ) {
8964                 // take nalimov path from the menu-changeable option first, if it is defined
8965               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8966                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8967             } else
8968             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8969                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8970                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8971                 s = r = StrStr(s, ":") + 1; // beginning of path info
8972                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8973                 c = *r; *r = 0;             // temporarily null-terminate path info
8974                     *--q = 0;               // strip of trailig ':' from name
8975                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8976                 *r = c;
8977                 SendToProgram(buf,cps);     // send egtbpath command for this format
8978             }
8979             if(*p == ',') p++; // read away comma to position for next format name
8980         }
8981 }
8982
8983 void
8984 InitChessProgram(cps, setup)
8985      ChessProgramState *cps;
8986      int setup; /* [HGM] needed to setup FRC opening position */
8987 {
8988     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8989     if (appData.noChessProgram) return;
8990     hintRequested = FALSE;
8991     bookRequested = FALSE;
8992
8993     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8994     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8995     if(cps->memSize) { /* [HGM] memory */
8996       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8997         SendToProgram(buf, cps);
8998     }
8999     SendEgtPath(cps); /* [HGM] EGT */
9000     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9001       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9002         SendToProgram(buf, cps);
9003     }
9004
9005     SendToProgram(cps->initString, cps);
9006     if (gameInfo.variant != VariantNormal &&
9007         gameInfo.variant != VariantLoadable
9008         /* [HGM] also send variant if board size non-standard */
9009         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9010                                             ) {
9011       char *v = VariantName(gameInfo.variant);
9012       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9013         /* [HGM] in protocol 1 we have to assume all variants valid */
9014         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9015         DisplayFatalError(buf, 0, 1);
9016         return;
9017       }
9018
9019       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9020       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9021       if( gameInfo.variant == VariantXiangqi )
9022            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9023       if( gameInfo.variant == VariantShogi )
9024            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9025       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9026            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9027       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9028           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9029            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9030       if( gameInfo.variant == VariantCourier )
9031            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9032       if( gameInfo.variant == VariantSuper )
9033            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9034       if( gameInfo.variant == VariantGreat )
9035            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9036       if( gameInfo.variant == VariantSChess )
9037            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9038
9039       if(overruled) {
9040         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9041                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9042            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9043            if(StrStr(cps->variants, b) == NULL) {
9044                // specific sized variant not known, check if general sizing allowed
9045                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9046                    if(StrStr(cps->variants, "boardsize") == NULL) {
9047                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9048                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9049                        DisplayFatalError(buf, 0, 1);
9050                        return;
9051                    }
9052                    /* [HGM] here we really should compare with the maximum supported board size */
9053                }
9054            }
9055       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9056       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9057       SendToProgram(buf, cps);
9058     }
9059     currentlyInitializedVariant = gameInfo.variant;
9060
9061     /* [HGM] send opening position in FRC to first engine */
9062     if(setup) {
9063           SendToProgram("force\n", cps);
9064           SendBoard(cps, 0);
9065           /* engine is now in force mode! Set flag to wake it up after first move. */
9066           setboardSpoiledMachineBlack = 1;
9067     }
9068
9069     if (cps->sendICS) {
9070       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9071       SendToProgram(buf, cps);
9072     }
9073     cps->maybeThinking = FALSE;
9074     cps->offeredDraw = 0;
9075     if (!appData.icsActive) {
9076         SendTimeControl(cps, movesPerSession, timeControl,
9077                         timeIncrement, appData.searchDepth,
9078                         searchTime);
9079     }
9080     if (appData.showThinking
9081         // [HGM] thinking: four options require thinking output to be sent
9082         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9083                                 ) {
9084         SendToProgram("post\n", cps);
9085     }
9086     SendToProgram("hard\n", cps);
9087     if (!appData.ponderNextMove) {
9088         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9089            it without being sure what state we are in first.  "hard"
9090            is not a toggle, so that one is OK.
9091          */
9092         SendToProgram("easy\n", cps);
9093     }
9094     if (cps->usePing) {
9095       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9096       SendToProgram(buf, cps);
9097     }
9098     cps->initDone = TRUE;
9099 }
9100
9101
9102 void
9103 StartChessProgram(cps)
9104      ChessProgramState *cps;
9105 {
9106     char buf[MSG_SIZ];
9107     int err;
9108
9109     if (appData.noChessProgram) return;
9110     cps->initDone = FALSE;
9111
9112     if (strcmp(cps->host, "localhost") == 0) {
9113         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9114     } else if (*appData.remoteShell == NULLCHAR) {
9115         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9116     } else {
9117         if (*appData.remoteUser == NULLCHAR) {
9118           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9119                     cps->program);
9120         } else {
9121           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9122                     cps->host, appData.remoteUser, cps->program);
9123         }
9124         err = StartChildProcess(buf, "", &cps->pr);
9125     }
9126
9127     if (err != 0) {
9128       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9129         DisplayFatalError(buf, err, 1);
9130         cps->pr = NoProc;
9131         cps->isr = NULL;
9132         return;
9133     }
9134
9135     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9136     if (cps->protocolVersion > 1) {
9137       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9138       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9139       cps->comboCnt = 0;  //                and values of combo boxes
9140       SendToProgram(buf, cps);
9141     } else {
9142       SendToProgram("xboard\n", cps);
9143     }
9144 }
9145
9146
9147 void
9148 TwoMachinesEventIfReady P((void))
9149 {
9150   if (first.lastPing != first.lastPong) {
9151     DisplayMessage("", _("Waiting for first chess program"));
9152     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9153     return;
9154   }
9155   if (second.lastPing != second.lastPong) {
9156     DisplayMessage("", _("Waiting for second chess program"));
9157     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9158     return;
9159   }
9160   ThawUI();
9161   TwoMachinesEvent();
9162 }
9163
9164 void
9165 NextMatchGame P((void))
9166 {
9167     int index; /* [HGM] autoinc: step load index during match */
9168     Reset(FALSE, TRUE);
9169     if (*appData.loadGameFile != NULLCHAR) {
9170         index = appData.loadGameIndex;
9171         if(index < 0) { // [HGM] autoinc
9172             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9173             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9174         }
9175         LoadGameFromFile(appData.loadGameFile,
9176                          index,
9177                          appData.loadGameFile, FALSE);
9178     } else if (*appData.loadPositionFile != NULLCHAR) {
9179         index = appData.loadPositionIndex;
9180         if(index < 0) { // [HGM] autoinc
9181             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9182             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9183         }
9184         LoadPositionFromFile(appData.loadPositionFile,
9185                              index,
9186                              appData.loadPositionFile);
9187     }
9188     TwoMachinesEventIfReady();
9189 }
9190
9191 void UserAdjudicationEvent( int result )
9192 {
9193     ChessMove gameResult = GameIsDrawn;
9194
9195     if( result > 0 ) {
9196         gameResult = WhiteWins;
9197     }
9198     else if( result < 0 ) {
9199         gameResult = BlackWins;
9200     }
9201
9202     if( gameMode == TwoMachinesPlay ) {
9203         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9204     }
9205 }
9206
9207
9208 // [HGM] save: calculate checksum of game to make games easily identifiable
9209 int StringCheckSum(char *s)
9210 {
9211         int i = 0;
9212         if(s==NULL) return 0;
9213         while(*s) i = i*259 + *s++;
9214         return i;
9215 }
9216
9217 int GameCheckSum()
9218 {
9219         int i, sum=0;
9220         for(i=backwardMostMove; i<forwardMostMove; i++) {
9221                 sum += pvInfoList[i].depth;
9222                 sum += StringCheckSum(parseList[i]);
9223                 sum += StringCheckSum(commentList[i]);
9224                 sum *= 261;
9225         }
9226         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9227         return sum + StringCheckSum(commentList[i]);
9228 } // end of save patch
9229
9230 void
9231 GameEnds(result, resultDetails, whosays)
9232      ChessMove result;
9233      char *resultDetails;
9234      int whosays;
9235 {
9236     GameMode nextGameMode;
9237     int isIcsGame;
9238     char buf[MSG_SIZ], popupRequested = 0;
9239
9240     if(endingGame) return; /* [HGM] crash: forbid recursion */
9241     endingGame = 1;
9242     if(twoBoards) { // [HGM] dual: switch back to one board
9243         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9244         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9245     }
9246     if (appData.debugMode) {
9247       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9248               result, resultDetails ? resultDetails : "(null)", whosays);
9249     }
9250
9251     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9252
9253     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9254         /* If we are playing on ICS, the server decides when the
9255            game is over, but the engine can offer to draw, claim
9256            a draw, or resign.
9257          */
9258 #if ZIPPY
9259         if (appData.zippyPlay && first.initDone) {
9260             if (result == GameIsDrawn) {
9261                 /* In case draw still needs to be claimed */
9262                 SendToICS(ics_prefix);
9263                 SendToICS("draw\n");
9264             } else if (StrCaseStr(resultDetails, "resign")) {
9265                 SendToICS(ics_prefix);
9266                 SendToICS("resign\n");
9267             }
9268         }
9269 #endif
9270         endingGame = 0; /* [HGM] crash */
9271         return;
9272     }
9273
9274     /* If we're loading the game from a file, stop */
9275     if (whosays == GE_FILE) {
9276       (void) StopLoadGameTimer();
9277       gameFileFP = NULL;
9278     }
9279
9280     /* Cancel draw offers */
9281     first.offeredDraw = second.offeredDraw = 0;
9282
9283     /* If this is an ICS game, only ICS can really say it's done;
9284        if not, anyone can. */
9285     isIcsGame = (gameMode == IcsPlayingWhite ||
9286                  gameMode == IcsPlayingBlack ||
9287                  gameMode == IcsObserving    ||
9288                  gameMode == IcsExamining);
9289
9290     if (!isIcsGame || whosays == GE_ICS) {
9291         /* OK -- not an ICS game, or ICS said it was done */
9292         StopClocks();
9293         if (!isIcsGame && !appData.noChessProgram)
9294           SetUserThinkingEnables();
9295
9296         /* [HGM] if a machine claims the game end we verify this claim */
9297         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9298             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9299                 char claimer;
9300                 ChessMove trueResult = (ChessMove) -1;
9301
9302                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9303                                             first.twoMachinesColor[0] :
9304                                             second.twoMachinesColor[0] ;
9305
9306                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9307                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9308                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9309                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9310                 } else
9311                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9312                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9313                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9314                 } else
9315                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9316                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9317                 }
9318
9319                 // now verify win claims, but not in drop games, as we don't understand those yet
9320                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9321                                                  || gameInfo.variant == VariantGreat) &&
9322                     (result == WhiteWins && claimer == 'w' ||
9323                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9324                       if (appData.debugMode) {
9325                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9326                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9327                       }
9328                       if(result != trueResult) {
9329                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9330                               result = claimer == 'w' ? BlackWins : WhiteWins;
9331                               resultDetails = buf;
9332                       }
9333                 } else
9334                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9335                     && (forwardMostMove <= backwardMostMove ||
9336                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9337                         (claimer=='b')==(forwardMostMove&1))
9338                                                                                   ) {
9339                       /* [HGM] verify: draws that were not flagged are false claims */
9340                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9341                       result = claimer == 'w' ? BlackWins : WhiteWins;
9342                       resultDetails = buf;
9343                 }
9344                 /* (Claiming a loss is accepted no questions asked!) */
9345             }
9346             /* [HGM] bare: don't allow bare King to win */
9347             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9348                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9349                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9350                && result != GameIsDrawn)
9351             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9352                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9353                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9354                         if(p >= 0 && p <= (int)WhiteKing) k++;
9355                 }
9356                 if (appData.debugMode) {
9357                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9358                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9359                 }
9360                 if(k <= 1) {
9361                         result = GameIsDrawn;
9362                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9363                         resultDetails = buf;
9364                 }
9365             }
9366         }
9367
9368
9369         if(serverMoves != NULL && !loadFlag) { char c = '=';
9370             if(result==WhiteWins) c = '+';
9371             if(result==BlackWins) c = '-';
9372             if(resultDetails != NULL)
9373                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9374         }
9375         if (resultDetails != NULL) {
9376             gameInfo.result = result;
9377             gameInfo.resultDetails = StrSave(resultDetails);
9378
9379             /* display last move only if game was not loaded from file */
9380             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9381                 DisplayMove(currentMove - 1);
9382
9383             if (forwardMostMove != 0) {
9384                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9385                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9386                                                                 ) {
9387                     if (*appData.saveGameFile != NULLCHAR) {
9388                         SaveGameToFile(appData.saveGameFile, TRUE);
9389                     } else if (appData.autoSaveGames) {
9390                         AutoSaveGame();
9391                     }
9392                     if (*appData.savePositionFile != NULLCHAR) {
9393                         SavePositionToFile(appData.savePositionFile);
9394                     }
9395                 }
9396             }
9397
9398             /* Tell program how game ended in case it is learning */
9399             /* [HGM] Moved this to after saving the PGN, just in case */
9400             /* engine died and we got here through time loss. In that */
9401             /* case we will get a fatal error writing the pipe, which */
9402             /* would otherwise lose us the PGN.                       */
9403             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9404             /* output during GameEnds should never be fatal anymore   */
9405             if (gameMode == MachinePlaysWhite ||
9406                 gameMode == MachinePlaysBlack ||
9407                 gameMode == TwoMachinesPlay ||
9408                 gameMode == IcsPlayingWhite ||
9409                 gameMode == IcsPlayingBlack ||
9410                 gameMode == BeginningOfGame) {
9411                 char buf[MSG_SIZ];
9412                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9413                         resultDetails);
9414                 if (first.pr != NoProc) {
9415                     SendToProgram(buf, &first);
9416                 }
9417                 if (second.pr != NoProc &&
9418                     gameMode == TwoMachinesPlay) {
9419                     SendToProgram(buf, &second);
9420                 }
9421             }
9422         }
9423
9424         if (appData.icsActive) {
9425             if (appData.quietPlay &&
9426                 (gameMode == IcsPlayingWhite ||
9427                  gameMode == IcsPlayingBlack)) {
9428                 SendToICS(ics_prefix);
9429                 SendToICS("set shout 1\n");
9430             }
9431             nextGameMode = IcsIdle;
9432             ics_user_moved = FALSE;
9433             /* clean up premove.  It's ugly when the game has ended and the
9434              * premove highlights are still on the board.
9435              */
9436             if (gotPremove) {
9437               gotPremove = FALSE;
9438               ClearPremoveHighlights();
9439               DrawPosition(FALSE, boards[currentMove]);
9440             }
9441             if (whosays == GE_ICS) {
9442                 switch (result) {
9443                 case WhiteWins:
9444                     if (gameMode == IcsPlayingWhite)
9445                         PlayIcsWinSound();
9446                     else if(gameMode == IcsPlayingBlack)
9447                         PlayIcsLossSound();
9448                     break;
9449                 case BlackWins:
9450                     if (gameMode == IcsPlayingBlack)
9451                         PlayIcsWinSound();
9452                     else if(gameMode == IcsPlayingWhite)
9453                         PlayIcsLossSound();
9454                     break;
9455                 case GameIsDrawn:
9456                     PlayIcsDrawSound();
9457                     break;
9458                 default:
9459                     PlayIcsUnfinishedSound();
9460                 }
9461             }
9462         } else if (gameMode == EditGame ||
9463                    gameMode == PlayFromGameFile ||
9464                    gameMode == AnalyzeMode ||
9465                    gameMode == AnalyzeFile) {
9466             nextGameMode = gameMode;
9467         } else {
9468             nextGameMode = EndOfGame;
9469         }
9470         pausing = FALSE;
9471         ModeHighlight();
9472     } else {
9473         nextGameMode = gameMode;
9474     }
9475
9476     if (appData.noChessProgram) {
9477         gameMode = nextGameMode;
9478         ModeHighlight();
9479         endingGame = 0; /* [HGM] crash */
9480         return;
9481     }
9482
9483     if (first.reuse) {
9484         /* Put first chess program into idle state */
9485         if (first.pr != NoProc &&
9486             (gameMode == MachinePlaysWhite ||
9487              gameMode == MachinePlaysBlack ||
9488              gameMode == TwoMachinesPlay ||
9489              gameMode == IcsPlayingWhite ||
9490              gameMode == IcsPlayingBlack ||
9491              gameMode == BeginningOfGame)) {
9492             SendToProgram("force\n", &first);
9493             if (first.usePing) {
9494               char buf[MSG_SIZ];
9495               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9496               SendToProgram(buf, &first);
9497             }
9498         }
9499     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9500         /* Kill off first chess program */
9501         if (first.isr != NULL)
9502           RemoveInputSource(first.isr);
9503         first.isr = NULL;
9504
9505         if (first.pr != NoProc) {
9506             ExitAnalyzeMode();
9507             DoSleep( appData.delayBeforeQuit );
9508             SendToProgram("quit\n", &first);
9509             DoSleep( appData.delayAfterQuit );
9510             DestroyChildProcess(first.pr, first.useSigterm);
9511         }
9512         first.pr = NoProc;
9513     }
9514     if (second.reuse) {
9515         /* Put second chess program into idle state */
9516         if (second.pr != NoProc &&
9517             gameMode == TwoMachinesPlay) {
9518             SendToProgram("force\n", &second);
9519             if (second.usePing) {
9520               char buf[MSG_SIZ];
9521               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9522               SendToProgram(buf, &second);
9523             }
9524         }
9525     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9526         /* Kill off second chess program */
9527         if (second.isr != NULL)
9528           RemoveInputSource(second.isr);
9529         second.isr = NULL;
9530
9531         if (second.pr != NoProc) {
9532             DoSleep( appData.delayBeforeQuit );
9533             SendToProgram("quit\n", &second);
9534             DoSleep( appData.delayAfterQuit );
9535             DestroyChildProcess(second.pr, second.useSigterm);
9536         }
9537         second.pr = NoProc;
9538     }
9539
9540     if (matchMode && gameMode == TwoMachinesPlay) {
9541         switch (result) {
9542         case WhiteWins:
9543           if (first.twoMachinesColor[0] == 'w') {
9544             first.matchWins++;
9545           } else {
9546             second.matchWins++;
9547           }
9548           break;
9549         case BlackWins:
9550           if (first.twoMachinesColor[0] == 'b') {
9551             first.matchWins++;
9552           } else {
9553             second.matchWins++;
9554           }
9555           break;
9556         default:
9557           break;
9558         }
9559         if (matchGame < appData.matchGames) {
9560             char *tmp;
9561             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9562                 tmp = first.twoMachinesColor;
9563                 first.twoMachinesColor = second.twoMachinesColor;
9564                 second.twoMachinesColor = tmp;
9565             }
9566             gameMode = nextGameMode;
9567             matchGame++;
9568             if(appData.matchPause>10000 || appData.matchPause<10)
9569                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9570             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9571             endingGame = 0; /* [HGM] crash */
9572             return;
9573         } else {
9574             gameMode = nextGameMode;
9575             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9576                      first.tidy, second.tidy,
9577                      first.matchWins, second.matchWins,
9578                      appData.matchGames - (first.matchWins + second.matchWins));
9579             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9580             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9581                 first.twoMachinesColor = "black\n";
9582                 second.twoMachinesColor = "white\n";
9583             } else {
9584                 first.twoMachinesColor = "white\n";
9585                 second.twoMachinesColor = "black\n";
9586             }
9587         }
9588     }
9589     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9590         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9591       ExitAnalyzeMode();
9592     gameMode = nextGameMode;
9593     ModeHighlight();
9594     endingGame = 0;  /* [HGM] crash */
9595     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9596       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9597         matchMode = FALSE; appData.matchGames = matchGame = 0;
9598         DisplayNote(buf);
9599       }
9600     }
9601 }
9602
9603 /* Assumes program was just initialized (initString sent).
9604    Leaves program in force mode. */
9605 void
9606 FeedMovesToProgram(cps, upto)
9607      ChessProgramState *cps;
9608      int upto;
9609 {
9610     int i;
9611
9612     if (appData.debugMode)
9613       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9614               startedFromSetupPosition ? "position and " : "",
9615               backwardMostMove, upto, cps->which);
9616     if(currentlyInitializedVariant != gameInfo.variant) {
9617       char buf[MSG_SIZ];
9618         // [HGM] variantswitch: make engine aware of new variant
9619         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9620                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9621         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9622         SendToProgram(buf, cps);
9623         currentlyInitializedVariant = gameInfo.variant;
9624     }
9625     SendToProgram("force\n", cps);
9626     if (startedFromSetupPosition) {
9627         SendBoard(cps, backwardMostMove);
9628     if (appData.debugMode) {
9629         fprintf(debugFP, "feedMoves\n");
9630     }
9631     }
9632     for (i = backwardMostMove; i < upto; i++) {
9633         SendMoveToProgram(i, cps);
9634     }
9635 }
9636
9637
9638 void
9639 ResurrectChessProgram()
9640 {
9641      /* The chess program may have exited.
9642         If so, restart it and feed it all the moves made so far. */
9643
9644     if (appData.noChessProgram || first.pr != NoProc) return;
9645
9646     StartChessProgram(&first);
9647     InitChessProgram(&first, FALSE);
9648     FeedMovesToProgram(&first, currentMove);
9649
9650     if (!first.sendTime) {
9651         /* can't tell gnuchess what its clock should read,
9652            so we bow to its notion. */
9653         ResetClocks();
9654         timeRemaining[0][currentMove] = whiteTimeRemaining;
9655         timeRemaining[1][currentMove] = blackTimeRemaining;
9656     }
9657
9658     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9659                 appData.icsEngineAnalyze) && first.analysisSupport) {
9660       SendToProgram("analyze\n", &first);
9661       first.analyzing = TRUE;
9662     }
9663 }
9664
9665 /*
9666  * Button procedures
9667  */
9668 void
9669 Reset(redraw, init)
9670      int redraw, init;
9671 {
9672     int i;
9673
9674     if (appData.debugMode) {
9675         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9676                 redraw, init, gameMode);
9677     }
9678     CleanupTail(); // [HGM] vari: delete any stored variations
9679     pausing = pauseExamInvalid = FALSE;
9680     startedFromSetupPosition = blackPlaysFirst = FALSE;
9681     firstMove = TRUE;
9682     whiteFlag = blackFlag = FALSE;
9683     userOfferedDraw = FALSE;
9684     hintRequested = bookRequested = FALSE;
9685     first.maybeThinking = FALSE;
9686     second.maybeThinking = FALSE;
9687     first.bookSuspend = FALSE; // [HGM] book
9688     second.bookSuspend = FALSE;
9689     thinkOutput[0] = NULLCHAR;
9690     lastHint[0] = NULLCHAR;
9691     ClearGameInfo(&gameInfo);
9692     gameInfo.variant = StringToVariant(appData.variant);
9693     ics_user_moved = ics_clock_paused = FALSE;
9694     ics_getting_history = H_FALSE;
9695     ics_gamenum = -1;
9696     white_holding[0] = black_holding[0] = NULLCHAR;
9697     ClearProgramStats();
9698     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9699
9700     ResetFrontEnd();
9701     ClearHighlights();
9702     flipView = appData.flipView;
9703     ClearPremoveHighlights();
9704     gotPremove = FALSE;
9705     alarmSounded = FALSE;
9706
9707     GameEnds(EndOfFile, NULL, GE_PLAYER);
9708     if(appData.serverMovesName != NULL) {
9709         /* [HGM] prepare to make moves file for broadcasting */
9710         clock_t t = clock();
9711         if(serverMoves != NULL) fclose(serverMoves);
9712         serverMoves = fopen(appData.serverMovesName, "r");
9713         if(serverMoves != NULL) {
9714             fclose(serverMoves);
9715             /* delay 15 sec before overwriting, so all clients can see end */
9716             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9717         }
9718         serverMoves = fopen(appData.serverMovesName, "w");
9719     }
9720
9721     ExitAnalyzeMode();
9722     gameMode = BeginningOfGame;
9723     ModeHighlight();
9724     if(appData.icsActive) gameInfo.variant = VariantNormal;
9725     currentMove = forwardMostMove = backwardMostMove = 0;
9726     InitPosition(redraw);
9727     for (i = 0; i < MAX_MOVES; i++) {
9728         if (commentList[i] != NULL) {
9729             free(commentList[i]);
9730             commentList[i] = NULL;
9731         }
9732     }
9733     ResetClocks();
9734     timeRemaining[0][0] = whiteTimeRemaining;
9735     timeRemaining[1][0] = blackTimeRemaining;
9736     if (first.pr == NULL) {
9737         StartChessProgram(&first);
9738     }
9739     if (init) {
9740             InitChessProgram(&first, startedFromSetupPosition);
9741     }
9742     DisplayTitle("");
9743     DisplayMessage("", "");
9744     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9745     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9746 }
9747
9748 void
9749 AutoPlayGameLoop()
9750 {
9751     for (;;) {
9752         if (!AutoPlayOneMove())
9753           return;
9754         if (matchMode || appData.timeDelay == 0)
9755           continue;
9756         if (appData.timeDelay < 0)
9757           return;
9758         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9759         break;
9760     }
9761 }
9762
9763
9764 int
9765 AutoPlayOneMove()
9766 {
9767     int fromX, fromY, toX, toY;
9768
9769     if (appData.debugMode) {
9770       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9771     }
9772
9773     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9774       return FALSE;
9775
9776     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9777       pvInfoList[currentMove].depth = programStats.depth;
9778       pvInfoList[currentMove].score = programStats.score;
9779       pvInfoList[currentMove].time  = 0;
9780       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9781     }
9782
9783     if (currentMove >= forwardMostMove) {
9784       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9785       gameMode = EditGame;
9786       ModeHighlight();
9787
9788       /* [AS] Clear current move marker at the end of a game */
9789       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9790
9791       return FALSE;
9792     }
9793
9794     toX = moveList[currentMove][2] - AAA;
9795     toY = moveList[currentMove][3] - ONE;
9796
9797     if (moveList[currentMove][1] == '@') {
9798         if (appData.highlightLastMove) {
9799             SetHighlights(-1, -1, toX, toY);
9800         }
9801     } else {
9802         fromX = moveList[currentMove][0] - AAA;
9803         fromY = moveList[currentMove][1] - ONE;
9804
9805         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9806
9807         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9808
9809         if (appData.highlightLastMove) {
9810             SetHighlights(fromX, fromY, toX, toY);
9811         }
9812     }
9813     DisplayMove(currentMove);
9814     SendMoveToProgram(currentMove++, &first);
9815     DisplayBothClocks();
9816     DrawPosition(FALSE, boards[currentMove]);
9817     // [HGM] PV info: always display, routine tests if empty
9818     DisplayComment(currentMove - 1, commentList[currentMove]);
9819     return TRUE;
9820 }
9821
9822
9823 int
9824 LoadGameOneMove(readAhead)
9825      ChessMove readAhead;
9826 {
9827     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9828     char promoChar = NULLCHAR;
9829     ChessMove moveType;
9830     char move[MSG_SIZ];
9831     char *p, *q;
9832
9833     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9834         gameMode != AnalyzeMode && gameMode != Training) {
9835         gameFileFP = NULL;
9836         return FALSE;
9837     }
9838
9839     yyboardindex = forwardMostMove;
9840     if (readAhead != EndOfFile) {
9841       moveType = readAhead;
9842     } else {
9843       if (gameFileFP == NULL)
9844           return FALSE;
9845       moveType = (ChessMove) Myylex();
9846     }
9847
9848     done = FALSE;
9849     switch (moveType) {
9850       case Comment:
9851         if (appData.debugMode)
9852           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9853         p = yy_text;
9854
9855         /* append the comment but don't display it */
9856         AppendComment(currentMove, p, FALSE);
9857         return TRUE;
9858
9859       case WhiteCapturesEnPassant:
9860       case BlackCapturesEnPassant:
9861       case WhitePromotion:
9862       case BlackPromotion:
9863       case WhiteNonPromotion:
9864       case BlackNonPromotion:
9865       case NormalMove:
9866       case WhiteKingSideCastle:
9867       case WhiteQueenSideCastle:
9868       case BlackKingSideCastle:
9869       case BlackQueenSideCastle:
9870       case WhiteKingSideCastleWild:
9871       case WhiteQueenSideCastleWild:
9872       case BlackKingSideCastleWild:
9873       case BlackQueenSideCastleWild:
9874       /* PUSH Fabien */
9875       case WhiteHSideCastleFR:
9876       case WhiteASideCastleFR:
9877       case BlackHSideCastleFR:
9878       case BlackASideCastleFR:
9879       /* POP Fabien */
9880         if (appData.debugMode)
9881           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9882         fromX = currentMoveString[0] - AAA;
9883         fromY = currentMoveString[1] - ONE;
9884         toX = currentMoveString[2] - AAA;
9885         toY = currentMoveString[3] - ONE;
9886         promoChar = currentMoveString[4];
9887         break;
9888
9889       case WhiteDrop:
9890       case BlackDrop:
9891         if (appData.debugMode)
9892           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9893         fromX = moveType == WhiteDrop ?
9894           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9895         (int) CharToPiece(ToLower(currentMoveString[0]));
9896         fromY = DROP_RANK;
9897         toX = currentMoveString[2] - AAA;
9898         toY = currentMoveString[3] - ONE;
9899         break;
9900
9901       case WhiteWins:
9902       case BlackWins:
9903       case GameIsDrawn:
9904       case GameUnfinished:
9905         if (appData.debugMode)
9906           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9907         p = strchr(yy_text, '{');
9908         if (p == NULL) p = strchr(yy_text, '(');
9909         if (p == NULL) {
9910             p = yy_text;
9911             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9912         } else {
9913             q = strchr(p, *p == '{' ? '}' : ')');
9914             if (q != NULL) *q = NULLCHAR;
9915             p++;
9916         }
9917         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9918         GameEnds(moveType, p, GE_FILE);
9919         done = TRUE;
9920         if (cmailMsgLoaded) {
9921             ClearHighlights();
9922             flipView = WhiteOnMove(currentMove);
9923             if (moveType == GameUnfinished) flipView = !flipView;
9924             if (appData.debugMode)
9925               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9926         }
9927         break;
9928
9929       case EndOfFile:
9930         if (appData.debugMode)
9931           fprintf(debugFP, "Parser hit end of file\n");
9932         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9933           case MT_NONE:
9934           case MT_CHECK:
9935             break;
9936           case MT_CHECKMATE:
9937           case MT_STAINMATE:
9938             if (WhiteOnMove(currentMove)) {
9939                 GameEnds(BlackWins, "Black mates", GE_FILE);
9940             } else {
9941                 GameEnds(WhiteWins, "White mates", GE_FILE);
9942             }
9943             break;
9944           case MT_STALEMATE:
9945             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9946             break;
9947         }
9948         done = TRUE;
9949         break;
9950
9951       case MoveNumberOne:
9952         if (lastLoadGameStart == GNUChessGame) {
9953             /* GNUChessGames have numbers, but they aren't move numbers */
9954             if (appData.debugMode)
9955               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9956                       yy_text, (int) moveType);
9957             return LoadGameOneMove(EndOfFile); /* tail recursion */
9958         }
9959         /* else fall thru */
9960
9961       case XBoardGame:
9962       case GNUChessGame:
9963       case PGNTag:
9964         /* Reached start of next game in file */
9965         if (appData.debugMode)
9966           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9967         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9968           case MT_NONE:
9969           case MT_CHECK:
9970             break;
9971           case MT_CHECKMATE:
9972           case MT_STAINMATE:
9973             if (WhiteOnMove(currentMove)) {
9974                 GameEnds(BlackWins, "Black mates", GE_FILE);
9975             } else {
9976                 GameEnds(WhiteWins, "White mates", GE_FILE);
9977             }
9978             break;
9979           case MT_STALEMATE:
9980             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9981             break;
9982         }
9983         done = TRUE;
9984         break;
9985
9986       case PositionDiagram:     /* should not happen; ignore */
9987       case ElapsedTime:         /* ignore */
9988       case NAG:                 /* ignore */
9989         if (appData.debugMode)
9990           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9991                   yy_text, (int) moveType);
9992         return LoadGameOneMove(EndOfFile); /* tail recursion */
9993
9994       case IllegalMove:
9995         if (appData.testLegality) {
9996             if (appData.debugMode)
9997               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9998             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9999                     (forwardMostMove / 2) + 1,
10000                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10001             DisplayError(move, 0);
10002             done = TRUE;
10003         } else {
10004             if (appData.debugMode)
10005               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10006                       yy_text, currentMoveString);
10007             fromX = currentMoveString[0] - AAA;
10008             fromY = currentMoveString[1] - ONE;
10009             toX = currentMoveString[2] - AAA;
10010             toY = currentMoveString[3] - ONE;
10011             promoChar = currentMoveString[4];
10012         }
10013         break;
10014
10015       case AmbiguousMove:
10016         if (appData.debugMode)
10017           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10018         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10019                 (forwardMostMove / 2) + 1,
10020                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10021         DisplayError(move, 0);
10022         done = TRUE;
10023         break;
10024
10025       default:
10026       case ImpossibleMove:
10027         if (appData.debugMode)
10028           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10029         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10030                 (forwardMostMove / 2) + 1,
10031                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10032         DisplayError(move, 0);
10033         done = TRUE;
10034         break;
10035     }
10036
10037     if (done) {
10038         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10039             DrawPosition(FALSE, boards[currentMove]);
10040             DisplayBothClocks();
10041             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10042               DisplayComment(currentMove - 1, commentList[currentMove]);
10043         }
10044         (void) StopLoadGameTimer();
10045         gameFileFP = NULL;
10046         cmailOldMove = forwardMostMove;
10047         return FALSE;
10048     } else {
10049         /* currentMoveString is set as a side-effect of yylex */
10050
10051         thinkOutput[0] = NULLCHAR;
10052         MakeMove(fromX, fromY, toX, toY, promoChar);
10053         currentMove = forwardMostMove;
10054         return TRUE;
10055     }
10056 }
10057
10058 /* Load the nth game from the given file */
10059 int
10060 LoadGameFromFile(filename, n, title, useList)
10061      char *filename;
10062      int n;
10063      char *title;
10064      /*Boolean*/ int useList;
10065 {
10066     FILE *f;
10067     char buf[MSG_SIZ];
10068
10069     if (strcmp(filename, "-") == 0) {
10070         f = stdin;
10071         title = "stdin";
10072     } else {
10073         f = fopen(filename, "rb");
10074         if (f == NULL) {
10075           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10076             DisplayError(buf, errno);
10077             return FALSE;
10078         }
10079     }
10080     if (fseek(f, 0, 0) == -1) {
10081         /* f is not seekable; probably a pipe */
10082         useList = FALSE;
10083     }
10084     if (useList && n == 0) {
10085         int error = GameListBuild(f);
10086         if (error) {
10087             DisplayError(_("Cannot build game list"), error);
10088         } else if (!ListEmpty(&gameList) &&
10089                    ((ListGame *) gameList.tailPred)->number > 1) {
10090             GameListPopUp(f, title);
10091             return TRUE;
10092         }
10093         GameListDestroy();
10094         n = 1;
10095     }
10096     if (n == 0) n = 1;
10097     return LoadGame(f, n, title, FALSE);
10098 }
10099
10100
10101 void
10102 MakeRegisteredMove()
10103 {
10104     int fromX, fromY, toX, toY;
10105     char promoChar;
10106     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10107         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10108           case CMAIL_MOVE:
10109           case CMAIL_DRAW:
10110             if (appData.debugMode)
10111               fprintf(debugFP, "Restoring %s for game %d\n",
10112                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10113
10114             thinkOutput[0] = NULLCHAR;
10115             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10116             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10117             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10118             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10119             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10120             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10121             MakeMove(fromX, fromY, toX, toY, promoChar);
10122             ShowMove(fromX, fromY, toX, toY);
10123
10124             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10125               case MT_NONE:
10126               case MT_CHECK:
10127                 break;
10128
10129               case MT_CHECKMATE:
10130               case MT_STAINMATE:
10131                 if (WhiteOnMove(currentMove)) {
10132                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10133                 } else {
10134                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10135                 }
10136                 break;
10137
10138               case MT_STALEMATE:
10139                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10140                 break;
10141             }
10142
10143             break;
10144
10145           case CMAIL_RESIGN:
10146             if (WhiteOnMove(currentMove)) {
10147                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10148             } else {
10149                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10150             }
10151             break;
10152
10153           case CMAIL_ACCEPT:
10154             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10155             break;
10156
10157           default:
10158             break;
10159         }
10160     }
10161
10162     return;
10163 }
10164
10165 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10166 int
10167 CmailLoadGame(f, gameNumber, title, useList)
10168      FILE *f;
10169      int gameNumber;
10170      char *title;
10171      int useList;
10172 {
10173     int retVal;
10174
10175     if (gameNumber > nCmailGames) {
10176         DisplayError(_("No more games in this message"), 0);
10177         return FALSE;
10178     }
10179     if (f == lastLoadGameFP) {
10180         int offset = gameNumber - lastLoadGameNumber;
10181         if (offset == 0) {
10182             cmailMsg[0] = NULLCHAR;
10183             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10184                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10185                 nCmailMovesRegistered--;
10186             }
10187             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10188             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10189                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10190             }
10191         } else {
10192             if (! RegisterMove()) return FALSE;
10193         }
10194     }
10195
10196     retVal = LoadGame(f, gameNumber, title, useList);
10197
10198     /* Make move registered during previous look at this game, if any */
10199     MakeRegisteredMove();
10200
10201     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10202         commentList[currentMove]
10203           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10204         DisplayComment(currentMove - 1, commentList[currentMove]);
10205     }
10206
10207     return retVal;
10208 }
10209
10210 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10211 int
10212 ReloadGame(offset)
10213      int offset;
10214 {
10215     int gameNumber = lastLoadGameNumber + offset;
10216     if (lastLoadGameFP == NULL) {
10217         DisplayError(_("No game has been loaded yet"), 0);
10218         return FALSE;
10219     }
10220     if (gameNumber <= 0) {
10221         DisplayError(_("Can't back up any further"), 0);
10222         return FALSE;
10223     }
10224     if (cmailMsgLoaded) {
10225         return CmailLoadGame(lastLoadGameFP, gameNumber,
10226                              lastLoadGameTitle, lastLoadGameUseList);
10227     } else {
10228         return LoadGame(lastLoadGameFP, gameNumber,
10229                         lastLoadGameTitle, lastLoadGameUseList);
10230     }
10231 }
10232
10233
10234
10235 /* Load the nth game from open file f */
10236 int
10237 LoadGame(f, gameNumber, title, useList)
10238      FILE *f;
10239      int gameNumber;
10240      char *title;
10241      int useList;
10242 {
10243     ChessMove cm;
10244     char buf[MSG_SIZ];
10245     int gn = gameNumber;
10246     ListGame *lg = NULL;
10247     int numPGNTags = 0;
10248     int err;
10249     GameMode oldGameMode;
10250     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10251
10252     if (appData.debugMode)
10253         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10254
10255     if (gameMode == Training )
10256         SetTrainingModeOff();
10257
10258     oldGameMode = gameMode;
10259     if (gameMode != BeginningOfGame) {
10260       Reset(FALSE, TRUE);
10261     }
10262
10263     gameFileFP = f;
10264     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10265         fclose(lastLoadGameFP);
10266     }
10267
10268     if (useList) {
10269         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10270
10271         if (lg) {
10272             fseek(f, lg->offset, 0);
10273             GameListHighlight(gameNumber);
10274             gn = 1;
10275         }
10276         else {
10277             DisplayError(_("Game number out of range"), 0);
10278             return FALSE;
10279         }
10280     } else {
10281         GameListDestroy();
10282         if (fseek(f, 0, 0) == -1) {
10283             if (f == lastLoadGameFP ?
10284                 gameNumber == lastLoadGameNumber + 1 :
10285                 gameNumber == 1) {
10286                 gn = 1;
10287             } else {
10288                 DisplayError(_("Can't seek on game file"), 0);
10289                 return FALSE;
10290             }
10291         }
10292     }
10293     lastLoadGameFP = f;
10294     lastLoadGameNumber = gameNumber;
10295     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10296     lastLoadGameUseList = useList;
10297
10298     yynewfile(f);
10299
10300     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10301       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10302                 lg->gameInfo.black);
10303             DisplayTitle(buf);
10304     } else if (*title != NULLCHAR) {
10305         if (gameNumber > 1) {
10306           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10307             DisplayTitle(buf);
10308         } else {
10309             DisplayTitle(title);
10310         }
10311     }
10312
10313     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10314         gameMode = PlayFromGameFile;
10315         ModeHighlight();
10316     }
10317
10318     currentMove = forwardMostMove = backwardMostMove = 0;
10319     CopyBoard(boards[0], initialPosition);
10320     StopClocks();
10321
10322     /*
10323      * Skip the first gn-1 games in the file.
10324      * Also skip over anything that precedes an identifiable
10325      * start of game marker, to avoid being confused by
10326      * garbage at the start of the file.  Currently
10327      * recognized start of game markers are the move number "1",
10328      * the pattern "gnuchess .* game", the pattern
10329      * "^[#;%] [^ ]* game file", and a PGN tag block.
10330      * A game that starts with one of the latter two patterns
10331      * will also have a move number 1, possibly
10332      * following a position diagram.
10333      * 5-4-02: Let's try being more lenient and allowing a game to
10334      * start with an unnumbered move.  Does that break anything?
10335      */
10336     cm = lastLoadGameStart = EndOfFile;
10337     while (gn > 0) {
10338         yyboardindex = forwardMostMove;
10339         cm = (ChessMove) Myylex();
10340         switch (cm) {
10341           case EndOfFile:
10342             if (cmailMsgLoaded) {
10343                 nCmailGames = CMAIL_MAX_GAMES - gn;
10344             } else {
10345                 Reset(TRUE, TRUE);
10346                 DisplayError(_("Game not found in file"), 0);
10347             }
10348             return FALSE;
10349
10350           case GNUChessGame:
10351           case XBoardGame:
10352             gn--;
10353             lastLoadGameStart = cm;
10354             break;
10355
10356           case MoveNumberOne:
10357             switch (lastLoadGameStart) {
10358               case GNUChessGame:
10359               case XBoardGame:
10360               case PGNTag:
10361                 break;
10362               case MoveNumberOne:
10363               case EndOfFile:
10364                 gn--;           /* count this game */
10365                 lastLoadGameStart = cm;
10366                 break;
10367               default:
10368                 /* impossible */
10369                 break;
10370             }
10371             break;
10372
10373           case PGNTag:
10374             switch (lastLoadGameStart) {
10375               case GNUChessGame:
10376               case PGNTag:
10377               case MoveNumberOne:
10378               case EndOfFile:
10379                 gn--;           /* count this game */
10380                 lastLoadGameStart = cm;
10381                 break;
10382               case XBoardGame:
10383                 lastLoadGameStart = cm; /* game counted already */
10384                 break;
10385               default:
10386                 /* impossible */
10387                 break;
10388             }
10389             if (gn > 0) {
10390                 do {
10391                     yyboardindex = forwardMostMove;
10392                     cm = (ChessMove) Myylex();
10393                 } while (cm == PGNTag || cm == Comment);
10394             }
10395             break;
10396
10397           case WhiteWins:
10398           case BlackWins:
10399           case GameIsDrawn:
10400             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10401                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10402                     != CMAIL_OLD_RESULT) {
10403                     nCmailResults ++ ;
10404                     cmailResult[  CMAIL_MAX_GAMES
10405                                 - gn - 1] = CMAIL_OLD_RESULT;
10406                 }
10407             }
10408             break;
10409
10410           case NormalMove:
10411             /* Only a NormalMove can be at the start of a game
10412              * without a position diagram. */
10413             if (lastLoadGameStart == EndOfFile ) {
10414               gn--;
10415               lastLoadGameStart = MoveNumberOne;
10416             }
10417             break;
10418
10419           default:
10420             break;
10421         }
10422     }
10423
10424     if (appData.debugMode)
10425       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10426
10427     if (cm == XBoardGame) {
10428         /* Skip any header junk before position diagram and/or move 1 */
10429         for (;;) {
10430             yyboardindex = forwardMostMove;
10431             cm = (ChessMove) Myylex();
10432
10433             if (cm == EndOfFile ||
10434                 cm == GNUChessGame || cm == XBoardGame) {
10435                 /* Empty game; pretend end-of-file and handle later */
10436                 cm = EndOfFile;
10437                 break;
10438             }
10439
10440             if (cm == MoveNumberOne || cm == PositionDiagram ||
10441                 cm == PGNTag || cm == Comment)
10442               break;
10443         }
10444     } else if (cm == GNUChessGame) {
10445         if (gameInfo.event != NULL) {
10446             free(gameInfo.event);
10447         }
10448         gameInfo.event = StrSave(yy_text);
10449     }
10450
10451     startedFromSetupPosition = FALSE;
10452     while (cm == PGNTag) {
10453         if (appData.debugMode)
10454           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10455         err = ParsePGNTag(yy_text, &gameInfo);
10456         if (!err) numPGNTags++;
10457
10458         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10459         if(gameInfo.variant != oldVariant) {
10460             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10461             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10462             InitPosition(TRUE);
10463             oldVariant = gameInfo.variant;
10464             if (appData.debugMode)
10465               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10466         }
10467
10468
10469         if (gameInfo.fen != NULL) {
10470           Board initial_position;
10471           startedFromSetupPosition = TRUE;
10472           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10473             Reset(TRUE, TRUE);
10474             DisplayError(_("Bad FEN position in file"), 0);
10475             return FALSE;
10476           }
10477           CopyBoard(boards[0], initial_position);
10478           if (blackPlaysFirst) {
10479             currentMove = forwardMostMove = backwardMostMove = 1;
10480             CopyBoard(boards[1], initial_position);
10481             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10482             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10483             timeRemaining[0][1] = whiteTimeRemaining;
10484             timeRemaining[1][1] = blackTimeRemaining;
10485             if (commentList[0] != NULL) {
10486               commentList[1] = commentList[0];
10487               commentList[0] = NULL;
10488             }
10489           } else {
10490             currentMove = forwardMostMove = backwardMostMove = 0;
10491           }
10492           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10493           {   int i;
10494               initialRulePlies = FENrulePlies;
10495               for( i=0; i< nrCastlingRights; i++ )
10496                   initialRights[i] = initial_position[CASTLING][i];
10497           }
10498           yyboardindex = forwardMostMove;
10499           free(gameInfo.fen);
10500           gameInfo.fen = NULL;
10501         }
10502
10503         yyboardindex = forwardMostMove;
10504         cm = (ChessMove) Myylex();
10505
10506         /* Handle comments interspersed among the tags */
10507         while (cm == Comment) {
10508             char *p;
10509             if (appData.debugMode)
10510               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10511             p = yy_text;
10512             AppendComment(currentMove, p, FALSE);
10513             yyboardindex = forwardMostMove;
10514             cm = (ChessMove) Myylex();
10515         }
10516     }
10517
10518     /* don't rely on existence of Event tag since if game was
10519      * pasted from clipboard the Event tag may not exist
10520      */
10521     if (numPGNTags > 0){
10522         char *tags;
10523         if (gameInfo.variant == VariantNormal) {
10524           VariantClass v = StringToVariant(gameInfo.event);
10525           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10526           if(v < VariantShogi) gameInfo.variant = v;
10527         }
10528         if (!matchMode) {
10529           if( appData.autoDisplayTags ) {
10530             tags = PGNTags(&gameInfo);
10531             TagsPopUp(tags, CmailMsg());
10532             free(tags);
10533           }
10534         }
10535     } else {
10536         /* Make something up, but don't display it now */
10537         SetGameInfo();
10538         TagsPopDown();
10539     }
10540
10541     if (cm == PositionDiagram) {
10542         int i, j;
10543         char *p;
10544         Board initial_position;
10545
10546         if (appData.debugMode)
10547           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10548
10549         if (!startedFromSetupPosition) {
10550             p = yy_text;
10551             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10552               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10553                 switch (*p) {
10554                   case '{':
10555                   case '[':
10556                   case '-':
10557                   case ' ':
10558                   case '\t':
10559                   case '\n':
10560                   case '\r':
10561                     break;
10562                   default:
10563                     initial_position[i][j++] = CharToPiece(*p);
10564                     break;
10565                 }
10566             while (*p == ' ' || *p == '\t' ||
10567                    *p == '\n' || *p == '\r') p++;
10568
10569             if (strncmp(p, "black", strlen("black"))==0)
10570               blackPlaysFirst = TRUE;
10571             else
10572               blackPlaysFirst = FALSE;
10573             startedFromSetupPosition = TRUE;
10574
10575             CopyBoard(boards[0], initial_position);
10576             if (blackPlaysFirst) {
10577                 currentMove = forwardMostMove = backwardMostMove = 1;
10578                 CopyBoard(boards[1], initial_position);
10579                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10580                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10581                 timeRemaining[0][1] = whiteTimeRemaining;
10582                 timeRemaining[1][1] = blackTimeRemaining;
10583                 if (commentList[0] != NULL) {
10584                     commentList[1] = commentList[0];
10585                     commentList[0] = NULL;
10586                 }
10587             } else {
10588                 currentMove = forwardMostMove = backwardMostMove = 0;
10589             }
10590         }
10591         yyboardindex = forwardMostMove;
10592         cm = (ChessMove) Myylex();
10593     }
10594
10595     if (first.pr == NoProc) {
10596         StartChessProgram(&first);
10597     }
10598     InitChessProgram(&first, FALSE);
10599     SendToProgram("force\n", &first);
10600     if (startedFromSetupPosition) {
10601         SendBoard(&first, forwardMostMove);
10602     if (appData.debugMode) {
10603         fprintf(debugFP, "Load Game\n");
10604     }
10605         DisplayBothClocks();
10606     }
10607
10608     /* [HGM] server: flag to write setup moves in broadcast file as one */
10609     loadFlag = appData.suppressLoadMoves;
10610
10611     while (cm == Comment) {
10612         char *p;
10613         if (appData.debugMode)
10614           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10615         p = yy_text;
10616         AppendComment(currentMove, p, FALSE);
10617         yyboardindex = forwardMostMove;
10618         cm = (ChessMove) Myylex();
10619     }
10620
10621     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10622         cm == WhiteWins || cm == BlackWins ||
10623         cm == GameIsDrawn || cm == GameUnfinished) {
10624         DisplayMessage("", _("No moves in game"));
10625         if (cmailMsgLoaded) {
10626             if (appData.debugMode)
10627               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10628             ClearHighlights();
10629             flipView = FALSE;
10630         }
10631         DrawPosition(FALSE, boards[currentMove]);
10632         DisplayBothClocks();
10633         gameMode = EditGame;
10634         ModeHighlight();
10635         gameFileFP = NULL;
10636         cmailOldMove = 0;
10637         return TRUE;
10638     }
10639
10640     // [HGM] PV info: routine tests if comment empty
10641     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10642         DisplayComment(currentMove - 1, commentList[currentMove]);
10643     }
10644     if (!matchMode && appData.timeDelay != 0)
10645       DrawPosition(FALSE, boards[currentMove]);
10646
10647     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10648       programStats.ok_to_send = 1;
10649     }
10650
10651     /* if the first token after the PGN tags is a move
10652      * and not move number 1, retrieve it from the parser
10653      */
10654     if (cm != MoveNumberOne)
10655         LoadGameOneMove(cm);
10656
10657     /* load the remaining moves from the file */
10658     while (LoadGameOneMove(EndOfFile)) {
10659       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10660       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10661     }
10662
10663     /* rewind to the start of the game */
10664     currentMove = backwardMostMove;
10665
10666     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10667
10668     if (oldGameMode == AnalyzeFile ||
10669         oldGameMode == AnalyzeMode) {
10670       AnalyzeFileEvent();
10671     }
10672
10673     if (matchMode || appData.timeDelay == 0) {
10674       ToEndEvent();
10675       gameMode = EditGame;
10676       ModeHighlight();
10677     } else if (appData.timeDelay > 0) {
10678       AutoPlayGameLoop();
10679     }
10680
10681     if (appData.debugMode)
10682         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10683
10684     loadFlag = 0; /* [HGM] true game starts */
10685     return TRUE;
10686 }
10687
10688 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10689 int
10690 ReloadPosition(offset)
10691      int offset;
10692 {
10693     int positionNumber = lastLoadPositionNumber + offset;
10694     if (lastLoadPositionFP == NULL) {
10695         DisplayError(_("No position has been loaded yet"), 0);
10696         return FALSE;
10697     }
10698     if (positionNumber <= 0) {
10699         DisplayError(_("Can't back up any further"), 0);
10700         return FALSE;
10701     }
10702     return LoadPosition(lastLoadPositionFP, positionNumber,
10703                         lastLoadPositionTitle);
10704 }
10705
10706 /* Load the nth position from the given file */
10707 int
10708 LoadPositionFromFile(filename, n, title)
10709      char *filename;
10710      int n;
10711      char *title;
10712 {
10713     FILE *f;
10714     char buf[MSG_SIZ];
10715
10716     if (strcmp(filename, "-") == 0) {
10717         return LoadPosition(stdin, n, "stdin");
10718     } else {
10719         f = fopen(filename, "rb");
10720         if (f == NULL) {
10721             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10722             DisplayError(buf, errno);
10723             return FALSE;
10724         } else {
10725             return LoadPosition(f, n, title);
10726         }
10727     }
10728 }
10729
10730 /* Load the nth position from the given open file, and close it */
10731 int
10732 LoadPosition(f, positionNumber, title)
10733      FILE *f;
10734      int positionNumber;
10735      char *title;
10736 {
10737     char *p, line[MSG_SIZ];
10738     Board initial_position;
10739     int i, j, fenMode, pn;
10740
10741     if (gameMode == Training )
10742         SetTrainingModeOff();
10743
10744     if (gameMode != BeginningOfGame) {
10745         Reset(FALSE, TRUE);
10746     }
10747     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10748         fclose(lastLoadPositionFP);
10749     }
10750     if (positionNumber == 0) positionNumber = 1;
10751     lastLoadPositionFP = f;
10752     lastLoadPositionNumber = positionNumber;
10753     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10754     if (first.pr == NoProc) {
10755       StartChessProgram(&first);
10756       InitChessProgram(&first, FALSE);
10757     }
10758     pn = positionNumber;
10759     if (positionNumber < 0) {
10760         /* Negative position number means to seek to that byte offset */
10761         if (fseek(f, -positionNumber, 0) == -1) {
10762             DisplayError(_("Can't seek on position file"), 0);
10763             return FALSE;
10764         };
10765         pn = 1;
10766     } else {
10767         if (fseek(f, 0, 0) == -1) {
10768             if (f == lastLoadPositionFP ?
10769                 positionNumber == lastLoadPositionNumber + 1 :
10770                 positionNumber == 1) {
10771                 pn = 1;
10772             } else {
10773                 DisplayError(_("Can't seek on position file"), 0);
10774                 return FALSE;
10775             }
10776         }
10777     }
10778     /* See if this file is FEN or old-style xboard */
10779     if (fgets(line, MSG_SIZ, f) == NULL) {
10780         DisplayError(_("Position not found in file"), 0);
10781         return FALSE;
10782     }
10783     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10784     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10785
10786     if (pn >= 2) {
10787         if (fenMode || line[0] == '#') pn--;
10788         while (pn > 0) {
10789             /* skip positions before number pn */
10790             if (fgets(line, MSG_SIZ, f) == NULL) {
10791                 Reset(TRUE, TRUE);
10792                 DisplayError(_("Position not found in file"), 0);
10793                 return FALSE;
10794             }
10795             if (fenMode || line[0] == '#') pn--;
10796         }
10797     }
10798
10799     if (fenMode) {
10800         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10801             DisplayError(_("Bad FEN position in file"), 0);
10802             return FALSE;
10803         }
10804     } else {
10805         (void) fgets(line, MSG_SIZ, f);
10806         (void) fgets(line, MSG_SIZ, f);
10807
10808         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10809             (void) fgets(line, MSG_SIZ, f);
10810             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10811                 if (*p == ' ')
10812                   continue;
10813                 initial_position[i][j++] = CharToPiece(*p);
10814             }
10815         }
10816
10817         blackPlaysFirst = FALSE;
10818         if (!feof(f)) {
10819             (void) fgets(line, MSG_SIZ, f);
10820             if (strncmp(line, "black", strlen("black"))==0)
10821               blackPlaysFirst = TRUE;
10822         }
10823     }
10824     startedFromSetupPosition = TRUE;
10825
10826     SendToProgram("force\n", &first);
10827     CopyBoard(boards[0], initial_position);
10828     if (blackPlaysFirst) {
10829         currentMove = forwardMostMove = backwardMostMove = 1;
10830         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10831         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10832         CopyBoard(boards[1], initial_position);
10833         DisplayMessage("", _("Black to play"));
10834     } else {
10835         currentMove = forwardMostMove = backwardMostMove = 0;
10836         DisplayMessage("", _("White to play"));
10837     }
10838     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10839     SendBoard(&first, forwardMostMove);
10840     if (appData.debugMode) {
10841 int i, j;
10842   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10843   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10844         fprintf(debugFP, "Load Position\n");
10845     }
10846
10847     if (positionNumber > 1) {
10848       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10849         DisplayTitle(line);
10850     } else {
10851         DisplayTitle(title);
10852     }
10853     gameMode = EditGame;
10854     ModeHighlight();
10855     ResetClocks();
10856     timeRemaining[0][1] = whiteTimeRemaining;
10857     timeRemaining[1][1] = blackTimeRemaining;
10858     DrawPosition(FALSE, boards[currentMove]);
10859
10860     return TRUE;
10861 }
10862
10863
10864 void
10865 CopyPlayerNameIntoFileName(dest, src)
10866      char **dest, *src;
10867 {
10868     while (*src != NULLCHAR && *src != ',') {
10869         if (*src == ' ') {
10870             *(*dest)++ = '_';
10871             src++;
10872         } else {
10873             *(*dest)++ = *src++;
10874         }
10875     }
10876 }
10877
10878 char *DefaultFileName(ext)
10879      char *ext;
10880 {
10881     static char def[MSG_SIZ];
10882     char *p;
10883
10884     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10885         p = def;
10886         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10887         *p++ = '-';
10888         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10889         *p++ = '.';
10890         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10891     } else {
10892         def[0] = NULLCHAR;
10893     }
10894     return def;
10895 }
10896
10897 /* Save the current game to the given file */
10898 int
10899 SaveGameToFile(filename, append)
10900      char *filename;
10901      int append;
10902 {
10903     FILE *f;
10904     char buf[MSG_SIZ];
10905
10906     if (strcmp(filename, "-") == 0) {
10907         return SaveGame(stdout, 0, NULL);
10908     } else {
10909         f = fopen(filename, append ? "a" : "w");
10910         if (f == NULL) {
10911             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10912             DisplayError(buf, errno);
10913             return FALSE;
10914         } else {
10915             return SaveGame(f, 0, NULL);
10916         }
10917     }
10918 }
10919
10920 char *
10921 SavePart(str)
10922      char *str;
10923 {
10924     static char buf[MSG_SIZ];
10925     char *p;
10926
10927     p = strchr(str, ' ');
10928     if (p == NULL) return str;
10929     strncpy(buf, str, p - str);
10930     buf[p - str] = NULLCHAR;
10931     return buf;
10932 }
10933
10934 #define PGN_MAX_LINE 75
10935
10936 #define PGN_SIDE_WHITE  0
10937 #define PGN_SIDE_BLACK  1
10938
10939 /* [AS] */
10940 static int FindFirstMoveOutOfBook( int side )
10941 {
10942     int result = -1;
10943
10944     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10945         int index = backwardMostMove;
10946         int has_book_hit = 0;
10947
10948         if( (index % 2) != side ) {
10949             index++;
10950         }
10951
10952         while( index < forwardMostMove ) {
10953             /* Check to see if engine is in book */
10954             int depth = pvInfoList[index].depth;
10955             int score = pvInfoList[index].score;
10956             int in_book = 0;
10957
10958             if( depth <= 2 ) {
10959                 in_book = 1;
10960             }
10961             else if( score == 0 && depth == 63 ) {
10962                 in_book = 1; /* Zappa */
10963             }
10964             else if( score == 2 && depth == 99 ) {
10965                 in_book = 1; /* Abrok */
10966             }
10967
10968             has_book_hit += in_book;
10969
10970             if( ! in_book ) {
10971                 result = index;
10972
10973                 break;
10974             }
10975
10976             index += 2;
10977         }
10978     }
10979
10980     return result;
10981 }
10982
10983 /* [AS] */
10984 void GetOutOfBookInfo( char * buf )
10985 {
10986     int oob[2];
10987     int i;
10988     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10989
10990     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10991     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10992
10993     *buf = '\0';
10994
10995     if( oob[0] >= 0 || oob[1] >= 0 ) {
10996         for( i=0; i<2; i++ ) {
10997             int idx = oob[i];
10998
10999             if( idx >= 0 ) {
11000                 if( i > 0 && oob[0] >= 0 ) {
11001                     strcat( buf, "   " );
11002                 }
11003
11004                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11005                 sprintf( buf+strlen(buf), "%s%.2f",
11006                     pvInfoList[idx].score >= 0 ? "+" : "",
11007                     pvInfoList[idx].score / 100.0 );
11008             }
11009         }
11010     }
11011 }
11012
11013 /* Save game in PGN style and close the file */
11014 int
11015 SaveGamePGN(f)
11016      FILE *f;
11017 {
11018     int i, offset, linelen, newblock;
11019     time_t tm;
11020 //    char *movetext;
11021     char numtext[32];
11022     int movelen, numlen, blank;
11023     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11024
11025     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11026
11027     tm = time((time_t *) NULL);
11028
11029     PrintPGNTags(f, &gameInfo);
11030
11031     if (backwardMostMove > 0 || startedFromSetupPosition) {
11032         char *fen = PositionToFEN(backwardMostMove, NULL);
11033         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11034         fprintf(f, "\n{--------------\n");
11035         PrintPosition(f, backwardMostMove);
11036         fprintf(f, "--------------}\n");
11037         free(fen);
11038     }
11039     else {
11040         /* [AS] Out of book annotation */
11041         if( appData.saveOutOfBookInfo ) {
11042             char buf[64];
11043
11044             GetOutOfBookInfo( buf );
11045
11046             if( buf[0] != '\0' ) {
11047                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11048             }
11049         }
11050
11051         fprintf(f, "\n");
11052     }
11053
11054     i = backwardMostMove;
11055     linelen = 0;
11056     newblock = TRUE;
11057
11058     while (i < forwardMostMove) {
11059         /* Print comments preceding this move */
11060         if (commentList[i] != NULL) {
11061             if (linelen > 0) fprintf(f, "\n");
11062             fprintf(f, "%s", commentList[i]);
11063             linelen = 0;
11064             newblock = TRUE;
11065         }
11066
11067         /* Format move number */
11068         if ((i % 2) == 0)
11069           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11070         else
11071           if (newblock)
11072             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11073           else
11074             numtext[0] = NULLCHAR;
11075
11076         numlen = strlen(numtext);
11077         newblock = FALSE;
11078
11079         /* Print move number */
11080         blank = linelen > 0 && numlen > 0;
11081         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11082             fprintf(f, "\n");
11083             linelen = 0;
11084             blank = 0;
11085         }
11086         if (blank) {
11087             fprintf(f, " ");
11088             linelen++;
11089         }
11090         fprintf(f, "%s", numtext);
11091         linelen += numlen;
11092
11093         /* Get move */
11094         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11095         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11096
11097         /* Print move */
11098         blank = linelen > 0 && movelen > 0;
11099         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11100             fprintf(f, "\n");
11101             linelen = 0;
11102             blank = 0;
11103         }
11104         if (blank) {
11105             fprintf(f, " ");
11106             linelen++;
11107         }
11108         fprintf(f, "%s", move_buffer);
11109         linelen += movelen;
11110
11111         /* [AS] Add PV info if present */
11112         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11113             /* [HGM] add time */
11114             char buf[MSG_SIZ]; int seconds;
11115
11116             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11117
11118             if( seconds <= 0)
11119               buf[0] = 0;
11120             else
11121               if( seconds < 30 )
11122                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11123               else
11124                 {
11125                   seconds = (seconds + 4)/10; // round to full seconds
11126                   if( seconds < 60 )
11127                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11128                   else
11129                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11130                 }
11131
11132             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11133                       pvInfoList[i].score >= 0 ? "+" : "",
11134                       pvInfoList[i].score / 100.0,
11135                       pvInfoList[i].depth,
11136                       buf );
11137
11138             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11139
11140             /* Print score/depth */
11141             blank = linelen > 0 && movelen > 0;
11142             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11143                 fprintf(f, "\n");
11144                 linelen = 0;
11145                 blank = 0;
11146             }
11147             if (blank) {
11148                 fprintf(f, " ");
11149                 linelen++;
11150             }
11151             fprintf(f, "%s", move_buffer);
11152             linelen += movelen;
11153         }
11154
11155         i++;
11156     }
11157
11158     /* Start a new line */
11159     if (linelen > 0) fprintf(f, "\n");
11160
11161     /* Print comments after last move */
11162     if (commentList[i] != NULL) {
11163         fprintf(f, "%s\n", commentList[i]);
11164     }
11165
11166     /* Print result */
11167     if (gameInfo.resultDetails != NULL &&
11168         gameInfo.resultDetails[0] != NULLCHAR) {
11169         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11170                 PGNResult(gameInfo.result));
11171     } else {
11172         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11173     }
11174
11175     fclose(f);
11176     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11177     return TRUE;
11178 }
11179
11180 /* Save game in old style and close the file */
11181 int
11182 SaveGameOldStyle(f)
11183      FILE *f;
11184 {
11185     int i, offset;
11186     time_t tm;
11187
11188     tm = time((time_t *) NULL);
11189
11190     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11191     PrintOpponents(f);
11192
11193     if (backwardMostMove > 0 || startedFromSetupPosition) {
11194         fprintf(f, "\n[--------------\n");
11195         PrintPosition(f, backwardMostMove);
11196         fprintf(f, "--------------]\n");
11197     } else {
11198         fprintf(f, "\n");
11199     }
11200
11201     i = backwardMostMove;
11202     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11203
11204     while (i < forwardMostMove) {
11205         if (commentList[i] != NULL) {
11206             fprintf(f, "[%s]\n", commentList[i]);
11207         }
11208
11209         if ((i % 2) == 1) {
11210             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11211             i++;
11212         } else {
11213             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11214             i++;
11215             if (commentList[i] != NULL) {
11216                 fprintf(f, "\n");
11217                 continue;
11218             }
11219             if (i >= forwardMostMove) {
11220                 fprintf(f, "\n");
11221                 break;
11222             }
11223             fprintf(f, "%s\n", parseList[i]);
11224             i++;
11225         }
11226     }
11227
11228     if (commentList[i] != NULL) {
11229         fprintf(f, "[%s]\n", commentList[i]);
11230     }
11231
11232     /* This isn't really the old style, but it's close enough */
11233     if (gameInfo.resultDetails != NULL &&
11234         gameInfo.resultDetails[0] != NULLCHAR) {
11235         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11236                 gameInfo.resultDetails);
11237     } else {
11238         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11239     }
11240
11241     fclose(f);
11242     return TRUE;
11243 }
11244
11245 /* Save the current game to open file f and close the file */
11246 int
11247 SaveGame(f, dummy, dummy2)
11248      FILE *f;
11249      int dummy;
11250      char *dummy2;
11251 {
11252     if (gameMode == EditPosition) EditPositionDone(TRUE);
11253     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11254     if (appData.oldSaveStyle)
11255       return SaveGameOldStyle(f);
11256     else
11257       return SaveGamePGN(f);
11258 }
11259
11260 /* Save the current position to the given file */
11261 int
11262 SavePositionToFile(filename)
11263      char *filename;
11264 {
11265     FILE *f;
11266     char buf[MSG_SIZ];
11267
11268     if (strcmp(filename, "-") == 0) {
11269         return SavePosition(stdout, 0, NULL);
11270     } else {
11271         f = fopen(filename, "a");
11272         if (f == NULL) {
11273             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11274             DisplayError(buf, errno);
11275             return FALSE;
11276         } else {
11277             SavePosition(f, 0, NULL);
11278             return TRUE;
11279         }
11280     }
11281 }
11282
11283 /* Save the current position to the given open file and close the file */
11284 int
11285 SavePosition(f, dummy, dummy2)
11286      FILE *f;
11287      int dummy;
11288      char *dummy2;
11289 {
11290     time_t tm;
11291     char *fen;
11292
11293     if (gameMode == EditPosition) EditPositionDone(TRUE);
11294     if (appData.oldSaveStyle) {
11295         tm = time((time_t *) NULL);
11296
11297         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11298         PrintOpponents(f);
11299         fprintf(f, "[--------------\n");
11300         PrintPosition(f, currentMove);
11301         fprintf(f, "--------------]\n");
11302     } else {
11303         fen = PositionToFEN(currentMove, NULL);
11304         fprintf(f, "%s\n", fen);
11305         free(fen);
11306     }
11307     fclose(f);
11308     return TRUE;
11309 }
11310
11311 void
11312 ReloadCmailMsgEvent(unregister)
11313      int unregister;
11314 {
11315 #if !WIN32
11316     static char *inFilename = NULL;
11317     static char *outFilename;
11318     int i;
11319     struct stat inbuf, outbuf;
11320     int status;
11321
11322     /* Any registered moves are unregistered if unregister is set, */
11323     /* i.e. invoked by the signal handler */
11324     if (unregister) {
11325         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11326             cmailMoveRegistered[i] = FALSE;
11327             if (cmailCommentList[i] != NULL) {
11328                 free(cmailCommentList[i]);
11329                 cmailCommentList[i] = NULL;
11330             }
11331         }
11332         nCmailMovesRegistered = 0;
11333     }
11334
11335     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11336         cmailResult[i] = CMAIL_NOT_RESULT;
11337     }
11338     nCmailResults = 0;
11339
11340     if (inFilename == NULL) {
11341         /* Because the filenames are static they only get malloced once  */
11342         /* and they never get freed                                      */
11343         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11344         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11345
11346         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11347         sprintf(outFilename, "%s.out", appData.cmailGameName);
11348     }
11349
11350     status = stat(outFilename, &outbuf);
11351     if (status < 0) {
11352         cmailMailedMove = FALSE;
11353     } else {
11354         status = stat(inFilename, &inbuf);
11355         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11356     }
11357
11358     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11359        counts the games, notes how each one terminated, etc.
11360
11361        It would be nice to remove this kludge and instead gather all
11362        the information while building the game list.  (And to keep it
11363        in the game list nodes instead of having a bunch of fixed-size
11364        parallel arrays.)  Note this will require getting each game's
11365        termination from the PGN tags, as the game list builder does
11366        not process the game moves.  --mann
11367        */
11368     cmailMsgLoaded = TRUE;
11369     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11370
11371     /* Load first game in the file or popup game menu */
11372     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11373
11374 #endif /* !WIN32 */
11375     return;
11376 }
11377
11378 int
11379 RegisterMove()
11380 {
11381     FILE *f;
11382     char string[MSG_SIZ];
11383
11384     if (   cmailMailedMove
11385         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11386         return TRUE;            /* Allow free viewing  */
11387     }
11388
11389     /* Unregister move to ensure that we don't leave RegisterMove        */
11390     /* with the move registered when the conditions for registering no   */
11391     /* longer hold                                                       */
11392     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11393         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11394         nCmailMovesRegistered --;
11395
11396         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11397           {
11398               free(cmailCommentList[lastLoadGameNumber - 1]);
11399               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11400           }
11401     }
11402
11403     if (cmailOldMove == -1) {
11404         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11405         return FALSE;
11406     }
11407
11408     if (currentMove > cmailOldMove + 1) {
11409         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11410         return FALSE;
11411     }
11412
11413     if (currentMove < cmailOldMove) {
11414         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11415         return FALSE;
11416     }
11417
11418     if (forwardMostMove > currentMove) {
11419         /* Silently truncate extra moves */
11420         TruncateGame();
11421     }
11422
11423     if (   (currentMove == cmailOldMove + 1)
11424         || (   (currentMove == cmailOldMove)
11425             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11426                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11427         if (gameInfo.result != GameUnfinished) {
11428             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11429         }
11430
11431         if (commentList[currentMove] != NULL) {
11432             cmailCommentList[lastLoadGameNumber - 1]
11433               = StrSave(commentList[currentMove]);
11434         }
11435         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11436
11437         if (appData.debugMode)
11438           fprintf(debugFP, "Saving %s for game %d\n",
11439                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11440
11441         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11442
11443         f = fopen(string, "w");
11444         if (appData.oldSaveStyle) {
11445             SaveGameOldStyle(f); /* also closes the file */
11446
11447             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11448             f = fopen(string, "w");
11449             SavePosition(f, 0, NULL); /* also closes the file */
11450         } else {
11451             fprintf(f, "{--------------\n");
11452             PrintPosition(f, currentMove);
11453             fprintf(f, "--------------}\n\n");
11454
11455             SaveGame(f, 0, NULL); /* also closes the file*/
11456         }
11457
11458         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11459         nCmailMovesRegistered ++;
11460     } else if (nCmailGames == 1) {
11461         DisplayError(_("You have not made a move yet"), 0);
11462         return FALSE;
11463     }
11464
11465     return TRUE;
11466 }
11467
11468 void
11469 MailMoveEvent()
11470 {
11471 #if !WIN32
11472     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11473     FILE *commandOutput;
11474     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11475     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11476     int nBuffers;
11477     int i;
11478     int archived;
11479     char *arcDir;
11480
11481     if (! cmailMsgLoaded) {
11482         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11483         return;
11484     }
11485
11486     if (nCmailGames == nCmailResults) {
11487         DisplayError(_("No unfinished games"), 0);
11488         return;
11489     }
11490
11491 #if CMAIL_PROHIBIT_REMAIL
11492     if (cmailMailedMove) {
11493       snprintf(msg, MSG_SIZ, _("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);
11494         DisplayError(msg, 0);
11495         return;
11496     }
11497 #endif
11498
11499     if (! (cmailMailedMove || RegisterMove())) return;
11500
11501     if (   cmailMailedMove
11502         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11503       snprintf(string, MSG_SIZ, partCommandString,
11504                appData.debugMode ? " -v" : "", appData.cmailGameName);
11505         commandOutput = popen(string, "r");
11506
11507         if (commandOutput == NULL) {
11508             DisplayError(_("Failed to invoke cmail"), 0);
11509         } else {
11510             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11511                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11512             }
11513             if (nBuffers > 1) {
11514                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11515                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11516                 nBytes = MSG_SIZ - 1;
11517             } else {
11518                 (void) memcpy(msg, buffer, nBytes);
11519             }
11520             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11521
11522             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11523                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11524
11525                 archived = TRUE;
11526                 for (i = 0; i < nCmailGames; i ++) {
11527                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11528                         archived = FALSE;
11529                     }
11530                 }
11531                 if (   archived
11532                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11533                         != NULL)) {
11534                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11535                            arcDir,
11536                            appData.cmailGameName,
11537                            gameInfo.date);
11538                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11539                     cmailMsgLoaded = FALSE;
11540                 }
11541             }
11542
11543             DisplayInformation(msg);
11544             pclose(commandOutput);
11545         }
11546     } else {
11547         if ((*cmailMsg) != '\0') {
11548             DisplayInformation(cmailMsg);
11549         }
11550     }
11551
11552     return;
11553 #endif /* !WIN32 */
11554 }
11555
11556 char *
11557 CmailMsg()
11558 {
11559 #if WIN32
11560     return NULL;
11561 #else
11562     int  prependComma = 0;
11563     char number[5];
11564     char string[MSG_SIZ];       /* Space for game-list */
11565     int  i;
11566
11567     if (!cmailMsgLoaded) return "";
11568
11569     if (cmailMailedMove) {
11570       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11571     } else {
11572         /* Create a list of games left */
11573       snprintf(string, MSG_SIZ, "[");
11574         for (i = 0; i < nCmailGames; i ++) {
11575             if (! (   cmailMoveRegistered[i]
11576                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11577                 if (prependComma) {
11578                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11579                 } else {
11580                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11581                     prependComma = 1;
11582                 }
11583
11584                 strcat(string, number);
11585             }
11586         }
11587         strcat(string, "]");
11588
11589         if (nCmailMovesRegistered + nCmailResults == 0) {
11590             switch (nCmailGames) {
11591               case 1:
11592                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11593                 break;
11594
11595               case 2:
11596                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11597                 break;
11598
11599               default:
11600                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11601                          nCmailGames);
11602                 break;
11603             }
11604         } else {
11605             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11606               case 1:
11607                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11608                          string);
11609                 break;
11610
11611               case 0:
11612                 if (nCmailResults == nCmailGames) {
11613                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11614                 } else {
11615                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11616                 }
11617                 break;
11618
11619               default:
11620                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11621                          string);
11622             }
11623         }
11624     }
11625     return cmailMsg;
11626 #endif /* WIN32 */
11627 }
11628
11629 void
11630 ResetGameEvent()
11631 {
11632     if (gameMode == Training)
11633       SetTrainingModeOff();
11634
11635     Reset(TRUE, TRUE);
11636     cmailMsgLoaded = FALSE;
11637     if (appData.icsActive) {
11638       SendToICS(ics_prefix);
11639       SendToICS("refresh\n");
11640     }
11641 }
11642
11643 void
11644 ExitEvent(status)
11645      int status;
11646 {
11647     exiting++;
11648     if (exiting > 2) {
11649       /* Give up on clean exit */
11650       exit(status);
11651     }
11652     if (exiting > 1) {
11653       /* Keep trying for clean exit */
11654       return;
11655     }
11656
11657     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11658
11659     if (telnetISR != NULL) {
11660       RemoveInputSource(telnetISR);
11661     }
11662     if (icsPR != NoProc) {
11663       DestroyChildProcess(icsPR, TRUE);
11664     }
11665
11666     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11667     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11668
11669     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11670     /* make sure this other one finishes before killing it!                  */
11671     if(endingGame) { int count = 0;
11672         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11673         while(endingGame && count++ < 10) DoSleep(1);
11674         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11675     }
11676
11677     /* Kill off chess programs */
11678     if (first.pr != NoProc) {
11679         ExitAnalyzeMode();
11680
11681         DoSleep( appData.delayBeforeQuit );
11682         SendToProgram("quit\n", &first);
11683         DoSleep( appData.delayAfterQuit );
11684         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11685     }
11686     if (second.pr != NoProc) {
11687         DoSleep( appData.delayBeforeQuit );
11688         SendToProgram("quit\n", &second);
11689         DoSleep( appData.delayAfterQuit );
11690         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11691     }
11692     if (first.isr != NULL) {
11693         RemoveInputSource(first.isr);
11694     }
11695     if (second.isr != NULL) {
11696         RemoveInputSource(second.isr);
11697     }
11698
11699     ShutDownFrontEnd();
11700     exit(status);
11701 }
11702
11703 void
11704 PauseEvent()
11705 {
11706     if (appData.debugMode)
11707         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11708     if (pausing) {
11709         pausing = FALSE;
11710         ModeHighlight();
11711         if (gameMode == MachinePlaysWhite ||
11712             gameMode == MachinePlaysBlack) {
11713             StartClocks();
11714         } else {
11715             DisplayBothClocks();
11716         }
11717         if (gameMode == PlayFromGameFile) {
11718             if (appData.timeDelay >= 0)
11719                 AutoPlayGameLoop();
11720         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11721             Reset(FALSE, TRUE);
11722             SendToICS(ics_prefix);
11723             SendToICS("refresh\n");
11724         } else if (currentMove < forwardMostMove) {
11725             ForwardInner(forwardMostMove);
11726         }
11727         pauseExamInvalid = FALSE;
11728     } else {
11729         switch (gameMode) {
11730           default:
11731             return;
11732           case IcsExamining:
11733             pauseExamForwardMostMove = forwardMostMove;
11734             pauseExamInvalid = FALSE;
11735             /* fall through */
11736           case IcsObserving:
11737           case IcsPlayingWhite:
11738           case IcsPlayingBlack:
11739             pausing = TRUE;
11740             ModeHighlight();
11741             return;
11742           case PlayFromGameFile:
11743             (void) StopLoadGameTimer();
11744             pausing = TRUE;
11745             ModeHighlight();
11746             break;
11747           case BeginningOfGame:
11748             if (appData.icsActive) return;
11749             /* else fall through */
11750           case MachinePlaysWhite:
11751           case MachinePlaysBlack:
11752           case TwoMachinesPlay:
11753             if (forwardMostMove == 0)
11754               return;           /* don't pause if no one has moved */
11755             if ((gameMode == MachinePlaysWhite &&
11756                  !WhiteOnMove(forwardMostMove)) ||
11757                 (gameMode == MachinePlaysBlack &&
11758                  WhiteOnMove(forwardMostMove))) {
11759                 StopClocks();
11760             }
11761             pausing = TRUE;
11762             ModeHighlight();
11763             break;
11764         }
11765     }
11766 }
11767
11768 void
11769 EditCommentEvent()
11770 {
11771     char title[MSG_SIZ];
11772
11773     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11774       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11775     } else {
11776       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11777                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11778                parseList[currentMove - 1]);
11779     }
11780
11781     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11782 }
11783
11784
11785 void
11786 EditTagsEvent()
11787 {
11788     char *tags = PGNTags(&gameInfo);
11789     EditTagsPopUp(tags, NULL);
11790     free(tags);
11791 }
11792
11793 void
11794 AnalyzeModeEvent()
11795 {
11796     if (appData.noChessProgram || gameMode == AnalyzeMode)
11797       return;
11798
11799     if (gameMode != AnalyzeFile) {
11800         if (!appData.icsEngineAnalyze) {
11801                EditGameEvent();
11802                if (gameMode != EditGame) return;
11803         }
11804         ResurrectChessProgram();
11805         SendToProgram("analyze\n", &first);
11806         first.analyzing = TRUE;
11807         /*first.maybeThinking = TRUE;*/
11808         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11809         EngineOutputPopUp();
11810     }
11811     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11812     pausing = FALSE;
11813     ModeHighlight();
11814     SetGameInfo();
11815
11816     StartAnalysisClock();
11817     GetTimeMark(&lastNodeCountTime);
11818     lastNodeCount = 0;
11819 }
11820
11821 void
11822 AnalyzeFileEvent()
11823 {
11824     if (appData.noChessProgram || gameMode == AnalyzeFile)
11825       return;
11826
11827     if (gameMode != AnalyzeMode) {
11828         EditGameEvent();
11829         if (gameMode != EditGame) return;
11830         ResurrectChessProgram();
11831         SendToProgram("analyze\n", &first);
11832         first.analyzing = TRUE;
11833         /*first.maybeThinking = TRUE;*/
11834         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11835         EngineOutputPopUp();
11836     }
11837     gameMode = AnalyzeFile;
11838     pausing = FALSE;
11839     ModeHighlight();
11840     SetGameInfo();
11841
11842     StartAnalysisClock();
11843     GetTimeMark(&lastNodeCountTime);
11844     lastNodeCount = 0;
11845 }
11846
11847 void
11848 MachineWhiteEvent()
11849 {
11850     char buf[MSG_SIZ];
11851     char *bookHit = NULL;
11852
11853     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11854       return;
11855
11856
11857     if (gameMode == PlayFromGameFile ||
11858         gameMode == TwoMachinesPlay  ||
11859         gameMode == Training         ||
11860         gameMode == AnalyzeMode      ||
11861         gameMode == EndOfGame)
11862         EditGameEvent();
11863
11864     if (gameMode == EditPosition)
11865         EditPositionDone(TRUE);
11866
11867     if (!WhiteOnMove(currentMove)) {
11868         DisplayError(_("It is not White's turn"), 0);
11869         return;
11870     }
11871
11872     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11873       ExitAnalyzeMode();
11874
11875     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11876         gameMode == AnalyzeFile)
11877         TruncateGame();
11878
11879     ResurrectChessProgram();    /* in case it isn't running */
11880     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11881         gameMode = MachinePlaysWhite;
11882         ResetClocks();
11883     } else
11884     gameMode = MachinePlaysWhite;
11885     pausing = FALSE;
11886     ModeHighlight();
11887     SetGameInfo();
11888     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11889     DisplayTitle(buf);
11890     if (first.sendName) {
11891       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11892       SendToProgram(buf, &first);
11893     }
11894     if (first.sendTime) {
11895       if (first.useColors) {
11896         SendToProgram("black\n", &first); /*gnu kludge*/
11897       }
11898       SendTimeRemaining(&first, TRUE);
11899     }
11900     if (first.useColors) {
11901       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11902     }
11903     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11904     SetMachineThinkingEnables();
11905     first.maybeThinking = TRUE;
11906     StartClocks();
11907     firstMove = FALSE;
11908
11909     if (appData.autoFlipView && !flipView) {
11910       flipView = !flipView;
11911       DrawPosition(FALSE, NULL);
11912       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11913     }
11914
11915     if(bookHit) { // [HGM] book: simulate book reply
11916         static char bookMove[MSG_SIZ]; // a bit generous?
11917
11918         programStats.nodes = programStats.depth = programStats.time =
11919         programStats.score = programStats.got_only_move = 0;
11920         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11921
11922         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11923         strcat(bookMove, bookHit);
11924         HandleMachineMove(bookMove, &first);
11925     }
11926 }
11927
11928 void
11929 MachineBlackEvent()
11930 {
11931   char buf[MSG_SIZ];
11932   char *bookHit = NULL;
11933
11934     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11935         return;
11936
11937
11938     if (gameMode == PlayFromGameFile ||
11939         gameMode == TwoMachinesPlay  ||
11940         gameMode == Training         ||
11941         gameMode == AnalyzeMode      ||
11942         gameMode == EndOfGame)
11943         EditGameEvent();
11944
11945     if (gameMode == EditPosition)
11946         EditPositionDone(TRUE);
11947
11948     if (WhiteOnMove(currentMove)) {
11949         DisplayError(_("It is not Black's turn"), 0);
11950         return;
11951     }
11952
11953     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11954       ExitAnalyzeMode();
11955
11956     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11957         gameMode == AnalyzeFile)
11958         TruncateGame();
11959
11960     ResurrectChessProgram();    /* in case it isn't running */
11961     gameMode = MachinePlaysBlack;
11962     pausing = FALSE;
11963     ModeHighlight();
11964     SetGameInfo();
11965     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11966     DisplayTitle(buf);
11967     if (first.sendName) {
11968       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11969       SendToProgram(buf, &first);
11970     }
11971     if (first.sendTime) {
11972       if (first.useColors) {
11973         SendToProgram("white\n", &first); /*gnu kludge*/
11974       }
11975       SendTimeRemaining(&first, FALSE);
11976     }
11977     if (first.useColors) {
11978       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11979     }
11980     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11981     SetMachineThinkingEnables();
11982     first.maybeThinking = TRUE;
11983     StartClocks();
11984
11985     if (appData.autoFlipView && flipView) {
11986       flipView = !flipView;
11987       DrawPosition(FALSE, NULL);
11988       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11989     }
11990     if(bookHit) { // [HGM] book: simulate book reply
11991         static char bookMove[MSG_SIZ]; // a bit generous?
11992
11993         programStats.nodes = programStats.depth = programStats.time =
11994         programStats.score = programStats.got_only_move = 0;
11995         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11996
11997         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11998         strcat(bookMove, bookHit);
11999         HandleMachineMove(bookMove, &first);
12000     }
12001 }
12002
12003
12004 void
12005 DisplayTwoMachinesTitle()
12006 {
12007     char buf[MSG_SIZ];
12008     if (appData.matchGames > 0) {
12009         if (first.twoMachinesColor[0] == 'w') {
12010           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12011                    gameInfo.white, gameInfo.black,
12012                    first.matchWins, second.matchWins,
12013                    matchGame - 1 - (first.matchWins + second.matchWins));
12014         } else {
12015           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12016                    gameInfo.white, gameInfo.black,
12017                    second.matchWins, first.matchWins,
12018                    matchGame - 1 - (first.matchWins + second.matchWins));
12019         }
12020     } else {
12021       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12022     }
12023     DisplayTitle(buf);
12024 }
12025
12026 void
12027 SettingsMenuIfReady()
12028 {
12029   if (second.lastPing != second.lastPong) {
12030     DisplayMessage("", _("Waiting for second chess program"));
12031     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12032     return;
12033   }
12034   ThawUI();
12035   DisplayMessage("", "");
12036   SettingsPopUp(&second);
12037 }
12038
12039 int
12040 WaitForSecond(DelayedEventCallback retry)
12041 {
12042     if (second.pr == NULL) {
12043         StartChessProgram(&second);
12044         if (second.protocolVersion == 1) {
12045           retry();
12046         } else {
12047           /* kludge: allow timeout for initial "feature" command */
12048           FreezeUI();
12049           DisplayMessage("", _("Starting second chess program"));
12050           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12051         }
12052         return 1;
12053     }
12054     return 0;
12055 }
12056
12057 void
12058 TwoMachinesEvent P((void))
12059 {
12060     int i;
12061     char buf[MSG_SIZ];
12062     ChessProgramState *onmove;
12063     char *bookHit = NULL;
12064
12065     if (appData.noChessProgram) return;
12066
12067     switch (gameMode) {
12068       case TwoMachinesPlay:
12069         return;
12070       case MachinePlaysWhite:
12071       case MachinePlaysBlack:
12072         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12073             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12074             return;
12075         }
12076         /* fall through */
12077       case BeginningOfGame:
12078       case PlayFromGameFile:
12079       case EndOfGame:
12080         EditGameEvent();
12081         if (gameMode != EditGame) return;
12082         break;
12083       case EditPosition:
12084         EditPositionDone(TRUE);
12085         break;
12086       case AnalyzeMode:
12087       case AnalyzeFile:
12088         ExitAnalyzeMode();
12089         break;
12090       case EditGame:
12091       default:
12092         break;
12093     }
12094
12095 //    forwardMostMove = currentMove;
12096     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12097     ResurrectChessProgram();    /* in case first program isn't running */
12098
12099     if(WaitForSecond(TwoMachinesEventIfReady)) return;
12100     DisplayMessage("", "");
12101     InitChessProgram(&second, FALSE);
12102     SendToProgram("force\n", &second);
12103     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12104       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12105       return;
12106     }
12107     if (startedFromSetupPosition) {
12108         SendBoard(&second, backwardMostMove);
12109     if (appData.debugMode) {
12110         fprintf(debugFP, "Two Machines\n");
12111     }
12112     }
12113     for (i = backwardMostMove; i < forwardMostMove; i++) {
12114         SendMoveToProgram(i, &second);
12115     }
12116
12117     gameMode = TwoMachinesPlay;
12118     pausing = FALSE;
12119     ModeHighlight();
12120     SetGameInfo();
12121     DisplayTwoMachinesTitle();
12122     firstMove = TRUE;
12123     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12124         onmove = &first;
12125     } else {
12126         onmove = &second;
12127     }
12128
12129     SendToProgram(first.computerString, &first);
12130     if (first.sendName) {
12131       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12132       SendToProgram(buf, &first);
12133     }
12134     SendToProgram(second.computerString, &second);
12135     if (second.sendName) {
12136       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12137       SendToProgram(buf, &second);
12138     }
12139
12140     ResetClocks();
12141     if (!first.sendTime || !second.sendTime) {
12142         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12143         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12144     }
12145     if (onmove->sendTime) {
12146       if (onmove->useColors) {
12147         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12148       }
12149       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12150     }
12151     if (onmove->useColors) {
12152       SendToProgram(onmove->twoMachinesColor, onmove);
12153     }
12154     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12155 //    SendToProgram("go\n", onmove);
12156     onmove->maybeThinking = TRUE;
12157     SetMachineThinkingEnables();
12158
12159     StartClocks();
12160
12161     if(bookHit) { // [HGM] book: simulate book reply
12162         static char bookMove[MSG_SIZ]; // a bit generous?
12163
12164         programStats.nodes = programStats.depth = programStats.time =
12165         programStats.score = programStats.got_only_move = 0;
12166         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12167
12168         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12169         strcat(bookMove, bookHit);
12170         savedMessage = bookMove; // args for deferred call
12171         savedState = onmove;
12172         ScheduleDelayedEvent(DeferredBookMove, 1);
12173     }
12174 }
12175
12176 void
12177 TrainingEvent()
12178 {
12179     if (gameMode == Training) {
12180       SetTrainingModeOff();
12181       gameMode = PlayFromGameFile;
12182       DisplayMessage("", _("Training mode off"));
12183     } else {
12184       gameMode = Training;
12185       animateTraining = appData.animate;
12186
12187       /* make sure we are not already at the end of the game */
12188       if (currentMove < forwardMostMove) {
12189         SetTrainingModeOn();
12190         DisplayMessage("", _("Training mode on"));
12191       } else {
12192         gameMode = PlayFromGameFile;
12193         DisplayError(_("Already at end of game"), 0);
12194       }
12195     }
12196     ModeHighlight();
12197 }
12198
12199 void
12200 IcsClientEvent()
12201 {
12202     if (!appData.icsActive) return;
12203     switch (gameMode) {
12204       case IcsPlayingWhite:
12205       case IcsPlayingBlack:
12206       case IcsObserving:
12207       case IcsIdle:
12208       case BeginningOfGame:
12209       case IcsExamining:
12210         return;
12211
12212       case EditGame:
12213         break;
12214
12215       case EditPosition:
12216         EditPositionDone(TRUE);
12217         break;
12218
12219       case AnalyzeMode:
12220       case AnalyzeFile:
12221         ExitAnalyzeMode();
12222         break;
12223
12224       default:
12225         EditGameEvent();
12226         break;
12227     }
12228
12229     gameMode = IcsIdle;
12230     ModeHighlight();
12231     return;
12232 }
12233
12234
12235 void
12236 EditGameEvent()
12237 {
12238     int i;
12239
12240     switch (gameMode) {
12241       case Training:
12242         SetTrainingModeOff();
12243         break;
12244       case MachinePlaysWhite:
12245       case MachinePlaysBlack:
12246       case BeginningOfGame:
12247         SendToProgram("force\n", &first);
12248         SetUserThinkingEnables();
12249         break;
12250       case PlayFromGameFile:
12251         (void) StopLoadGameTimer();
12252         if (gameFileFP != NULL) {
12253             gameFileFP = NULL;
12254         }
12255         break;
12256       case EditPosition:
12257         EditPositionDone(TRUE);
12258         break;
12259       case AnalyzeMode:
12260       case AnalyzeFile:
12261         ExitAnalyzeMode();
12262         SendToProgram("force\n", &first);
12263         break;
12264       case TwoMachinesPlay:
12265         GameEnds(EndOfFile, NULL, GE_PLAYER);
12266         ResurrectChessProgram();
12267         SetUserThinkingEnables();
12268         break;
12269       case EndOfGame:
12270         ResurrectChessProgram();
12271         break;
12272       case IcsPlayingBlack:
12273       case IcsPlayingWhite:
12274         DisplayError(_("Warning: You are still playing a game"), 0);
12275         break;
12276       case IcsObserving:
12277         DisplayError(_("Warning: You are still observing a game"), 0);
12278         break;
12279       case IcsExamining:
12280         DisplayError(_("Warning: You are still examining a game"), 0);
12281         break;
12282       case IcsIdle:
12283         break;
12284       case EditGame:
12285       default:
12286         return;
12287     }
12288
12289     pausing = FALSE;
12290     StopClocks();
12291     first.offeredDraw = second.offeredDraw = 0;
12292
12293     if (gameMode == PlayFromGameFile) {
12294         whiteTimeRemaining = timeRemaining[0][currentMove];
12295         blackTimeRemaining = timeRemaining[1][currentMove];
12296         DisplayTitle("");
12297     }
12298
12299     if (gameMode == MachinePlaysWhite ||
12300         gameMode == MachinePlaysBlack ||
12301         gameMode == TwoMachinesPlay ||
12302         gameMode == EndOfGame) {
12303         i = forwardMostMove;
12304         while (i > currentMove) {
12305             SendToProgram("undo\n", &first);
12306             i--;
12307         }
12308         whiteTimeRemaining = timeRemaining[0][currentMove];
12309         blackTimeRemaining = timeRemaining[1][currentMove];
12310         DisplayBothClocks();
12311         if (whiteFlag || blackFlag) {
12312             whiteFlag = blackFlag = 0;
12313         }
12314         DisplayTitle("");
12315     }
12316
12317     gameMode = EditGame;
12318     ModeHighlight();
12319     SetGameInfo();
12320 }
12321
12322
12323 void
12324 EditPositionEvent()
12325 {
12326     if (gameMode == EditPosition) {
12327         EditGameEvent();
12328         return;
12329     }
12330
12331     EditGameEvent();
12332     if (gameMode != EditGame) return;
12333
12334     gameMode = EditPosition;
12335     ModeHighlight();
12336     SetGameInfo();
12337     if (currentMove > 0)
12338       CopyBoard(boards[0], boards[currentMove]);
12339
12340     blackPlaysFirst = !WhiteOnMove(currentMove);
12341     ResetClocks();
12342     currentMove = forwardMostMove = backwardMostMove = 0;
12343     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12344     DisplayMove(-1);
12345 }
12346
12347 void
12348 ExitAnalyzeMode()
12349 {
12350     /* [DM] icsEngineAnalyze - possible call from other functions */
12351     if (appData.icsEngineAnalyze) {
12352         appData.icsEngineAnalyze = FALSE;
12353
12354         DisplayMessage("",_("Close ICS engine analyze..."));
12355     }
12356     if (first.analysisSupport && first.analyzing) {
12357       SendToProgram("exit\n", &first);
12358       first.analyzing = FALSE;
12359     }
12360     thinkOutput[0] = NULLCHAR;
12361 }
12362
12363 void
12364 EditPositionDone(Boolean fakeRights)
12365 {
12366     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12367
12368     startedFromSetupPosition = TRUE;
12369     InitChessProgram(&first, FALSE);
12370     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12371       boards[0][EP_STATUS] = EP_NONE;
12372       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12373     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12374         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12375         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12376       } else boards[0][CASTLING][2] = NoRights;
12377     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12378         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12379         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12380       } else boards[0][CASTLING][5] = NoRights;
12381     }
12382     SendToProgram("force\n", &first);
12383     if (blackPlaysFirst) {
12384         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12385         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12386         currentMove = forwardMostMove = backwardMostMove = 1;
12387         CopyBoard(boards[1], boards[0]);
12388     } else {
12389         currentMove = forwardMostMove = backwardMostMove = 0;
12390     }
12391     SendBoard(&first, forwardMostMove);
12392     if (appData.debugMode) {
12393         fprintf(debugFP, "EditPosDone\n");
12394     }
12395     DisplayTitle("");
12396     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12397     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12398     gameMode = EditGame;
12399     ModeHighlight();
12400     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12401     ClearHighlights(); /* [AS] */
12402 }
12403
12404 /* Pause for `ms' milliseconds */
12405 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12406 void
12407 TimeDelay(ms)
12408      long ms;
12409 {
12410     TimeMark m1, m2;
12411
12412     GetTimeMark(&m1);
12413     do {
12414         GetTimeMark(&m2);
12415     } while (SubtractTimeMarks(&m2, &m1) < ms);
12416 }
12417
12418 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12419 void
12420 SendMultiLineToICS(buf)
12421      char *buf;
12422 {
12423     char temp[MSG_SIZ+1], *p;
12424     int len;
12425
12426     len = strlen(buf);
12427     if (len > MSG_SIZ)
12428       len = MSG_SIZ;
12429
12430     strncpy(temp, buf, len);
12431     temp[len] = 0;
12432
12433     p = temp;
12434     while (*p) {
12435         if (*p == '\n' || *p == '\r')
12436           *p = ' ';
12437         ++p;
12438     }
12439
12440     strcat(temp, "\n");
12441     SendToICS(temp);
12442     SendToPlayer(temp, strlen(temp));
12443 }
12444
12445 void
12446 SetWhiteToPlayEvent()
12447 {
12448     if (gameMode == EditPosition) {
12449         blackPlaysFirst = FALSE;
12450         DisplayBothClocks();    /* works because currentMove is 0 */
12451     } else if (gameMode == IcsExamining) {
12452         SendToICS(ics_prefix);
12453         SendToICS("tomove white\n");
12454     }
12455 }
12456
12457 void
12458 SetBlackToPlayEvent()
12459 {
12460     if (gameMode == EditPosition) {
12461         blackPlaysFirst = TRUE;
12462         currentMove = 1;        /* kludge */
12463         DisplayBothClocks();
12464         currentMove = 0;
12465     } else if (gameMode == IcsExamining) {
12466         SendToICS(ics_prefix);
12467         SendToICS("tomove black\n");
12468     }
12469 }
12470
12471 void
12472 EditPositionMenuEvent(selection, x, y)
12473      ChessSquare selection;
12474      int x, y;
12475 {
12476     char buf[MSG_SIZ];
12477     ChessSquare piece = boards[0][y][x];
12478
12479     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12480
12481     switch (selection) {
12482       case ClearBoard:
12483         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12484             SendToICS(ics_prefix);
12485             SendToICS("bsetup clear\n");
12486         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12487             SendToICS(ics_prefix);
12488             SendToICS("clearboard\n");
12489         } else {
12490             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12491                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12492                 for (y = 0; y < BOARD_HEIGHT; y++) {
12493                     if (gameMode == IcsExamining) {
12494                         if (boards[currentMove][y][x] != EmptySquare) {
12495                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12496                                     AAA + x, ONE + y);
12497                             SendToICS(buf);
12498                         }
12499                     } else {
12500                         boards[0][y][x] = p;
12501                     }
12502                 }
12503             }
12504         }
12505         if (gameMode == EditPosition) {
12506             DrawPosition(FALSE, boards[0]);
12507         }
12508         break;
12509
12510       case WhitePlay:
12511         SetWhiteToPlayEvent();
12512         break;
12513
12514       case BlackPlay:
12515         SetBlackToPlayEvent();
12516         break;
12517
12518       case EmptySquare:
12519         if (gameMode == IcsExamining) {
12520             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12521             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12522             SendToICS(buf);
12523         } else {
12524             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12525                 if(x == BOARD_LEFT-2) {
12526                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12527                     boards[0][y][1] = 0;
12528                 } else
12529                 if(x == BOARD_RGHT+1) {
12530                     if(y >= gameInfo.holdingsSize) break;
12531                     boards[0][y][BOARD_WIDTH-2] = 0;
12532                 } else break;
12533             }
12534             boards[0][y][x] = EmptySquare;
12535             DrawPosition(FALSE, boards[0]);
12536         }
12537         break;
12538
12539       case PromotePiece:
12540         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12541            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12542             selection = (ChessSquare) (PROMOTED piece);
12543         } else if(piece == EmptySquare) selection = WhiteSilver;
12544         else selection = (ChessSquare)((int)piece - 1);
12545         goto defaultlabel;
12546
12547       case DemotePiece:
12548         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12549            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12550             selection = (ChessSquare) (DEMOTED piece);
12551         } else if(piece == EmptySquare) selection = BlackSilver;
12552         else selection = (ChessSquare)((int)piece + 1);
12553         goto defaultlabel;
12554
12555       case WhiteQueen:
12556       case BlackQueen:
12557         if(gameInfo.variant == VariantShatranj ||
12558            gameInfo.variant == VariantXiangqi  ||
12559            gameInfo.variant == VariantCourier  ||
12560            gameInfo.variant == VariantMakruk     )
12561             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12562         goto defaultlabel;
12563
12564       case WhiteKing:
12565       case BlackKing:
12566         if(gameInfo.variant == VariantXiangqi)
12567             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12568         if(gameInfo.variant == VariantKnightmate)
12569             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12570       default:
12571         defaultlabel:
12572         if (gameMode == IcsExamining) {
12573             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12574             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12575                      PieceToChar(selection), AAA + x, ONE + y);
12576             SendToICS(buf);
12577         } else {
12578             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12579                 int n;
12580                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12581                     n = PieceToNumber(selection - BlackPawn);
12582                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12583                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12584                     boards[0][BOARD_HEIGHT-1-n][1]++;
12585                 } else
12586                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12587                     n = PieceToNumber(selection);
12588                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12589                     boards[0][n][BOARD_WIDTH-1] = selection;
12590                     boards[0][n][BOARD_WIDTH-2]++;
12591                 }
12592             } else
12593             boards[0][y][x] = selection;
12594             DrawPosition(TRUE, boards[0]);
12595         }
12596         break;
12597     }
12598 }
12599
12600
12601 void
12602 DropMenuEvent(selection, x, y)
12603      ChessSquare selection;
12604      int x, y;
12605 {
12606     ChessMove moveType;
12607
12608     switch (gameMode) {
12609       case IcsPlayingWhite:
12610       case MachinePlaysBlack:
12611         if (!WhiteOnMove(currentMove)) {
12612             DisplayMoveError(_("It is Black's turn"));
12613             return;
12614         }
12615         moveType = WhiteDrop;
12616         break;
12617       case IcsPlayingBlack:
12618       case MachinePlaysWhite:
12619         if (WhiteOnMove(currentMove)) {
12620             DisplayMoveError(_("It is White's turn"));
12621             return;
12622         }
12623         moveType = BlackDrop;
12624         break;
12625       case EditGame:
12626         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12627         break;
12628       default:
12629         return;
12630     }
12631
12632     if (moveType == BlackDrop && selection < BlackPawn) {
12633       selection = (ChessSquare) ((int) selection
12634                                  + (int) BlackPawn - (int) WhitePawn);
12635     }
12636     if (boards[currentMove][y][x] != EmptySquare) {
12637         DisplayMoveError(_("That square is occupied"));
12638         return;
12639     }
12640
12641     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12642 }
12643
12644 void
12645 AcceptEvent()
12646 {
12647     /* Accept a pending offer of any kind from opponent */
12648
12649     if (appData.icsActive) {
12650         SendToICS(ics_prefix);
12651         SendToICS("accept\n");
12652     } else if (cmailMsgLoaded) {
12653         if (currentMove == cmailOldMove &&
12654             commentList[cmailOldMove] != NULL &&
12655             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12656                    "Black offers a draw" : "White offers a draw")) {
12657             TruncateGame();
12658             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12659             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12660         } else {
12661             DisplayError(_("There is no pending offer on this move"), 0);
12662             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12663         }
12664     } else {
12665         /* Not used for offers from chess program */
12666     }
12667 }
12668
12669 void
12670 DeclineEvent()
12671 {
12672     /* Decline a pending offer of any kind from opponent */
12673
12674     if (appData.icsActive) {
12675         SendToICS(ics_prefix);
12676         SendToICS("decline\n");
12677     } else if (cmailMsgLoaded) {
12678         if (currentMove == cmailOldMove &&
12679             commentList[cmailOldMove] != NULL &&
12680             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12681                    "Black offers a draw" : "White offers a draw")) {
12682 #ifdef NOTDEF
12683             AppendComment(cmailOldMove, "Draw declined", TRUE);
12684             DisplayComment(cmailOldMove - 1, "Draw declined");
12685 #endif /*NOTDEF*/
12686         } else {
12687             DisplayError(_("There is no pending offer on this move"), 0);
12688         }
12689     } else {
12690         /* Not used for offers from chess program */
12691     }
12692 }
12693
12694 void
12695 RematchEvent()
12696 {
12697     /* Issue ICS rematch command */
12698     if (appData.icsActive) {
12699         SendToICS(ics_prefix);
12700         SendToICS("rematch\n");
12701     }
12702 }
12703
12704 void
12705 CallFlagEvent()
12706 {
12707     /* Call your opponent's flag (claim a win on time) */
12708     if (appData.icsActive) {
12709         SendToICS(ics_prefix);
12710         SendToICS("flag\n");
12711     } else {
12712         switch (gameMode) {
12713           default:
12714             return;
12715           case MachinePlaysWhite:
12716             if (whiteFlag) {
12717                 if (blackFlag)
12718                   GameEnds(GameIsDrawn, "Both players ran out of time",
12719                            GE_PLAYER);
12720                 else
12721                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12722             } else {
12723                 DisplayError(_("Your opponent is not out of time"), 0);
12724             }
12725             break;
12726           case MachinePlaysBlack:
12727             if (blackFlag) {
12728                 if (whiteFlag)
12729                   GameEnds(GameIsDrawn, "Both players ran out of time",
12730                            GE_PLAYER);
12731                 else
12732                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12733             } else {
12734                 DisplayError(_("Your opponent is not out of time"), 0);
12735             }
12736             break;
12737         }
12738     }
12739 }
12740
12741 void
12742 ClockClick(int which)
12743 {       // [HGM] code moved to back-end from winboard.c
12744         if(which) { // black clock
12745           if (gameMode == EditPosition || gameMode == IcsExamining) {
12746             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12747             SetBlackToPlayEvent();
12748           } else if (gameMode == EditGame || shiftKey) {
12749             AdjustClock(which, -1);
12750           } else if (gameMode == IcsPlayingWhite ||
12751                      gameMode == MachinePlaysBlack) {
12752             CallFlagEvent();
12753           }
12754         } else { // white clock
12755           if (gameMode == EditPosition || gameMode == IcsExamining) {
12756             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12757             SetWhiteToPlayEvent();
12758           } else if (gameMode == EditGame || shiftKey) {
12759             AdjustClock(which, -1);
12760           } else if (gameMode == IcsPlayingBlack ||
12761                    gameMode == MachinePlaysWhite) {
12762             CallFlagEvent();
12763           }
12764         }
12765 }
12766
12767 void
12768 DrawEvent()
12769 {
12770     /* Offer draw or accept pending draw offer from opponent */
12771
12772     if (appData.icsActive) {
12773         /* Note: tournament rules require draw offers to be
12774            made after you make your move but before you punch
12775            your clock.  Currently ICS doesn't let you do that;
12776            instead, you immediately punch your clock after making
12777            a move, but you can offer a draw at any time. */
12778
12779         SendToICS(ics_prefix);
12780         SendToICS("draw\n");
12781         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12782     } else if (cmailMsgLoaded) {
12783         if (currentMove == cmailOldMove &&
12784             commentList[cmailOldMove] != NULL &&
12785             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12786                    "Black offers a draw" : "White offers a draw")) {
12787             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12788             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12789         } else if (currentMove == cmailOldMove + 1) {
12790             char *offer = WhiteOnMove(cmailOldMove) ?
12791               "White offers a draw" : "Black offers a draw";
12792             AppendComment(currentMove, offer, TRUE);
12793             DisplayComment(currentMove - 1, offer);
12794             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12795         } else {
12796             DisplayError(_("You must make your move before offering a draw"), 0);
12797             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12798         }
12799     } else if (first.offeredDraw) {
12800         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12801     } else {
12802         if (first.sendDrawOffers) {
12803             SendToProgram("draw\n", &first);
12804             userOfferedDraw = TRUE;
12805         }
12806     }
12807 }
12808
12809 void
12810 AdjournEvent()
12811 {
12812     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12813
12814     if (appData.icsActive) {
12815         SendToICS(ics_prefix);
12816         SendToICS("adjourn\n");
12817     } else {
12818         /* Currently GNU Chess doesn't offer or accept Adjourns */
12819     }
12820 }
12821
12822
12823 void
12824 AbortEvent()
12825 {
12826     /* Offer Abort or accept pending Abort offer from opponent */
12827
12828     if (appData.icsActive) {
12829         SendToICS(ics_prefix);
12830         SendToICS("abort\n");
12831     } else {
12832         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12833     }
12834 }
12835
12836 void
12837 ResignEvent()
12838 {
12839     /* Resign.  You can do this even if it's not your turn. */
12840
12841     if (appData.icsActive) {
12842         SendToICS(ics_prefix);
12843         SendToICS("resign\n");
12844     } else {
12845         switch (gameMode) {
12846           case MachinePlaysWhite:
12847             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12848             break;
12849           case MachinePlaysBlack:
12850             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12851             break;
12852           case EditGame:
12853             if (cmailMsgLoaded) {
12854                 TruncateGame();
12855                 if (WhiteOnMove(cmailOldMove)) {
12856                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12857                 } else {
12858                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12859                 }
12860                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12861             }
12862             break;
12863           default:
12864             break;
12865         }
12866     }
12867 }
12868
12869
12870 void
12871 StopObservingEvent()
12872 {
12873     /* Stop observing current games */
12874     SendToICS(ics_prefix);
12875     SendToICS("unobserve\n");
12876 }
12877
12878 void
12879 StopExaminingEvent()
12880 {
12881     /* Stop observing current game */
12882     SendToICS(ics_prefix);
12883     SendToICS("unexamine\n");
12884 }
12885
12886 void
12887 ForwardInner(target)
12888      int target;
12889 {
12890     int limit;
12891
12892     if (appData.debugMode)
12893         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12894                 target, currentMove, forwardMostMove);
12895
12896     if (gameMode == EditPosition)
12897       return;
12898
12899     if (gameMode == PlayFromGameFile && !pausing)
12900       PauseEvent();
12901
12902     if (gameMode == IcsExamining && pausing)
12903       limit = pauseExamForwardMostMove;
12904     else
12905       limit = forwardMostMove;
12906
12907     if (target > limit) target = limit;
12908
12909     if (target > 0 && moveList[target - 1][0]) {
12910         int fromX, fromY, toX, toY;
12911         toX = moveList[target - 1][2] - AAA;
12912         toY = moveList[target - 1][3] - ONE;
12913         if (moveList[target - 1][1] == '@') {
12914             if (appData.highlightLastMove) {
12915                 SetHighlights(-1, -1, toX, toY);
12916             }
12917         } else {
12918             fromX = moveList[target - 1][0] - AAA;
12919             fromY = moveList[target - 1][1] - ONE;
12920             if (target == currentMove + 1) {
12921                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12922             }
12923             if (appData.highlightLastMove) {
12924                 SetHighlights(fromX, fromY, toX, toY);
12925             }
12926         }
12927     }
12928     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12929         gameMode == Training || gameMode == PlayFromGameFile ||
12930         gameMode == AnalyzeFile) {
12931         while (currentMove < target) {
12932             SendMoveToProgram(currentMove++, &first);
12933         }
12934     } else {
12935         currentMove = target;
12936     }
12937
12938     if (gameMode == EditGame || gameMode == EndOfGame) {
12939         whiteTimeRemaining = timeRemaining[0][currentMove];
12940         blackTimeRemaining = timeRemaining[1][currentMove];
12941     }
12942     DisplayBothClocks();
12943     DisplayMove(currentMove - 1);
12944     DrawPosition(FALSE, boards[currentMove]);
12945     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12946     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12947         DisplayComment(currentMove - 1, commentList[currentMove]);
12948     }
12949 }
12950
12951
12952 void
12953 ForwardEvent()
12954 {
12955     if (gameMode == IcsExamining && !pausing) {
12956         SendToICS(ics_prefix);
12957         SendToICS("forward\n");
12958     } else {
12959         ForwardInner(currentMove + 1);
12960     }
12961 }
12962
12963 void
12964 ToEndEvent()
12965 {
12966     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12967         /* to optimze, we temporarily turn off analysis mode while we feed
12968          * the remaining moves to the engine. Otherwise we get analysis output
12969          * after each move.
12970          */
12971         if (first.analysisSupport) {
12972           SendToProgram("exit\nforce\n", &first);
12973           first.analyzing = FALSE;
12974         }
12975     }
12976
12977     if (gameMode == IcsExamining && !pausing) {
12978         SendToICS(ics_prefix);
12979         SendToICS("forward 999999\n");
12980     } else {
12981         ForwardInner(forwardMostMove);
12982     }
12983
12984     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12985         /* we have fed all the moves, so reactivate analysis mode */
12986         SendToProgram("analyze\n", &first);
12987         first.analyzing = TRUE;
12988         /*first.maybeThinking = TRUE;*/
12989         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12990     }
12991 }
12992
12993 void
12994 BackwardInner(target)
12995      int target;
12996 {
12997     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12998
12999     if (appData.debugMode)
13000         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13001                 target, currentMove, forwardMostMove);
13002
13003     if (gameMode == EditPosition) return;
13004     if (currentMove <= backwardMostMove) {
13005         ClearHighlights();
13006         DrawPosition(full_redraw, boards[currentMove]);
13007         return;
13008     }
13009     if (gameMode == PlayFromGameFile && !pausing)
13010       PauseEvent();
13011
13012     if (moveList[target][0]) {
13013         int fromX, fromY, toX, toY;
13014         toX = moveList[target][2] - AAA;
13015         toY = moveList[target][3] - ONE;
13016         if (moveList[target][1] == '@') {
13017             if (appData.highlightLastMove) {
13018                 SetHighlights(-1, -1, toX, toY);
13019             }
13020         } else {
13021             fromX = moveList[target][0] - AAA;
13022             fromY = moveList[target][1] - ONE;
13023             if (target == currentMove - 1) {
13024                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13025             }
13026             if (appData.highlightLastMove) {
13027                 SetHighlights(fromX, fromY, toX, toY);
13028             }
13029         }
13030     }
13031     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13032         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13033         while (currentMove > target) {
13034             SendToProgram("undo\n", &first);
13035             currentMove--;
13036         }
13037     } else {
13038         currentMove = target;
13039     }
13040
13041     if (gameMode == EditGame || gameMode == EndOfGame) {
13042         whiteTimeRemaining = timeRemaining[0][currentMove];
13043         blackTimeRemaining = timeRemaining[1][currentMove];
13044     }
13045     DisplayBothClocks();
13046     DisplayMove(currentMove - 1);
13047     DrawPosition(full_redraw, boards[currentMove]);
13048     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13049     // [HGM] PV info: routine tests if comment empty
13050     DisplayComment(currentMove - 1, commentList[currentMove]);
13051 }
13052
13053 void
13054 BackwardEvent()
13055 {
13056     if (gameMode == IcsExamining && !pausing) {
13057         SendToICS(ics_prefix);
13058         SendToICS("backward\n");
13059     } else {
13060         BackwardInner(currentMove - 1);
13061     }
13062 }
13063
13064 void
13065 ToStartEvent()
13066 {
13067     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13068         /* to optimize, we temporarily turn off analysis mode while we undo
13069          * all the moves. Otherwise we get analysis output after each undo.
13070          */
13071         if (first.analysisSupport) {
13072           SendToProgram("exit\nforce\n", &first);
13073           first.analyzing = FALSE;
13074         }
13075     }
13076
13077     if (gameMode == IcsExamining && !pausing) {
13078         SendToICS(ics_prefix);
13079         SendToICS("backward 999999\n");
13080     } else {
13081         BackwardInner(backwardMostMove);
13082     }
13083
13084     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13085         /* we have fed all the moves, so reactivate analysis mode */
13086         SendToProgram("analyze\n", &first);
13087         first.analyzing = TRUE;
13088         /*first.maybeThinking = TRUE;*/
13089         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13090     }
13091 }
13092
13093 void
13094 ToNrEvent(int to)
13095 {
13096   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13097   if (to >= forwardMostMove) to = forwardMostMove;
13098   if (to <= backwardMostMove) to = backwardMostMove;
13099   if (to < currentMove) {
13100     BackwardInner(to);
13101   } else {
13102     ForwardInner(to);
13103   }
13104 }
13105
13106 void
13107 RevertEvent(Boolean annotate)
13108 {
13109     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13110         return;
13111     }
13112     if (gameMode != IcsExamining) {
13113         DisplayError(_("You are not examining a game"), 0);
13114         return;
13115     }
13116     if (pausing) {
13117         DisplayError(_("You can't revert while pausing"), 0);
13118         return;
13119     }
13120     SendToICS(ics_prefix);
13121     SendToICS("revert\n");
13122 }
13123
13124 void
13125 RetractMoveEvent()
13126 {
13127     switch (gameMode) {
13128       case MachinePlaysWhite:
13129       case MachinePlaysBlack:
13130         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13131             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13132             return;
13133         }
13134         if (forwardMostMove < 2) return;
13135         currentMove = forwardMostMove = forwardMostMove - 2;
13136         whiteTimeRemaining = timeRemaining[0][currentMove];
13137         blackTimeRemaining = timeRemaining[1][currentMove];
13138         DisplayBothClocks();
13139         DisplayMove(currentMove - 1);
13140         ClearHighlights();/*!! could figure this out*/
13141         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13142         SendToProgram("remove\n", &first);
13143         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13144         break;
13145
13146       case BeginningOfGame:
13147       default:
13148         break;
13149
13150       case IcsPlayingWhite:
13151       case IcsPlayingBlack:
13152         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13153             SendToICS(ics_prefix);
13154             SendToICS("takeback 2\n");
13155         } else {
13156             SendToICS(ics_prefix);
13157             SendToICS("takeback 1\n");
13158         }
13159         break;
13160     }
13161 }
13162
13163 void
13164 MoveNowEvent()
13165 {
13166     ChessProgramState *cps;
13167
13168     switch (gameMode) {
13169       case MachinePlaysWhite:
13170         if (!WhiteOnMove(forwardMostMove)) {
13171             DisplayError(_("It is your turn"), 0);
13172             return;
13173         }
13174         cps = &first;
13175         break;
13176       case MachinePlaysBlack:
13177         if (WhiteOnMove(forwardMostMove)) {
13178             DisplayError(_("It is your turn"), 0);
13179             return;
13180         }
13181         cps = &first;
13182         break;
13183       case TwoMachinesPlay:
13184         if (WhiteOnMove(forwardMostMove) ==
13185             (first.twoMachinesColor[0] == 'w')) {
13186             cps = &first;
13187         } else {
13188             cps = &second;
13189         }
13190         break;
13191       case BeginningOfGame:
13192       default:
13193         return;
13194     }
13195     SendToProgram("?\n", cps);
13196 }
13197
13198 void
13199 TruncateGameEvent()
13200 {
13201     EditGameEvent();
13202     if (gameMode != EditGame) return;
13203     TruncateGame();
13204 }
13205
13206 void
13207 TruncateGame()
13208 {
13209     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13210     if (forwardMostMove > currentMove) {
13211         if (gameInfo.resultDetails != NULL) {
13212             free(gameInfo.resultDetails);
13213             gameInfo.resultDetails = NULL;
13214             gameInfo.result = GameUnfinished;
13215         }
13216         forwardMostMove = currentMove;
13217         HistorySet(parseList, backwardMostMove, forwardMostMove,
13218                    currentMove-1);
13219     }
13220 }
13221
13222 void
13223 HintEvent()
13224 {
13225     if (appData.noChessProgram) return;
13226     switch (gameMode) {
13227       case MachinePlaysWhite:
13228         if (WhiteOnMove(forwardMostMove)) {
13229             DisplayError(_("Wait until your turn"), 0);
13230             return;
13231         }
13232         break;
13233       case BeginningOfGame:
13234       case MachinePlaysBlack:
13235         if (!WhiteOnMove(forwardMostMove)) {
13236             DisplayError(_("Wait until your turn"), 0);
13237             return;
13238         }
13239         break;
13240       default:
13241         DisplayError(_("No hint available"), 0);
13242         return;
13243     }
13244     SendToProgram("hint\n", &first);
13245     hintRequested = TRUE;
13246 }
13247
13248 void
13249 BookEvent()
13250 {
13251     if (appData.noChessProgram) return;
13252     switch (gameMode) {
13253       case MachinePlaysWhite:
13254         if (WhiteOnMove(forwardMostMove)) {
13255             DisplayError(_("Wait until your turn"), 0);
13256             return;
13257         }
13258         break;
13259       case BeginningOfGame:
13260       case MachinePlaysBlack:
13261         if (!WhiteOnMove(forwardMostMove)) {
13262             DisplayError(_("Wait until your turn"), 0);
13263             return;
13264         }
13265         break;
13266       case EditPosition:
13267         EditPositionDone(TRUE);
13268         break;
13269       case TwoMachinesPlay:
13270         return;
13271       default:
13272         break;
13273     }
13274     SendToProgram("bk\n", &first);
13275     bookOutput[0] = NULLCHAR;
13276     bookRequested = TRUE;
13277 }
13278
13279 void
13280 AboutGameEvent()
13281 {
13282     char *tags = PGNTags(&gameInfo);
13283     TagsPopUp(tags, CmailMsg());
13284     free(tags);
13285 }
13286
13287 /* end button procedures */
13288
13289 void
13290 PrintPosition(fp, move)
13291      FILE *fp;
13292      int move;
13293 {
13294     int i, j;
13295
13296     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13297         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13298             char c = PieceToChar(boards[move][i][j]);
13299             fputc(c == 'x' ? '.' : c, fp);
13300             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13301         }
13302     }
13303     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13304       fprintf(fp, "white to play\n");
13305     else
13306       fprintf(fp, "black to play\n");
13307 }
13308
13309 void
13310 PrintOpponents(fp)
13311      FILE *fp;
13312 {
13313     if (gameInfo.white != NULL) {
13314         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13315     } else {
13316         fprintf(fp, "\n");
13317     }
13318 }
13319
13320 /* Find last component of program's own name, using some heuristics */
13321 void
13322 TidyProgramName(prog, host, buf)
13323      char *prog, *host, buf[MSG_SIZ];
13324 {
13325     char *p, *q;
13326     int local = (strcmp(host, "localhost") == 0);
13327     while (!local && (p = strchr(prog, ';')) != NULL) {
13328         p++;
13329         while (*p == ' ') p++;
13330         prog = p;
13331     }
13332     if (*prog == '"' || *prog == '\'') {
13333         q = strchr(prog + 1, *prog);
13334     } else {
13335         q = strchr(prog, ' ');
13336     }
13337     if (q == NULL) q = prog + strlen(prog);
13338     p = q;
13339     while (p >= prog && *p != '/' && *p != '\\') p--;
13340     p++;
13341     if(p == prog && *p == '"') p++;
13342     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13343     memcpy(buf, p, q - p);
13344     buf[q - p] = NULLCHAR;
13345     if (!local) {
13346         strcat(buf, "@");
13347         strcat(buf, host);
13348     }
13349 }
13350
13351 char *
13352 TimeControlTagValue()
13353 {
13354     char buf[MSG_SIZ];
13355     if (!appData.clockMode) {
13356       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13357     } else if (movesPerSession > 0) {
13358       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13359     } else if (timeIncrement == 0) {
13360       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13361     } else {
13362       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13363     }
13364     return StrSave(buf);
13365 }
13366
13367 void
13368 SetGameInfo()
13369 {
13370     /* This routine is used only for certain modes */
13371     VariantClass v = gameInfo.variant;
13372     ChessMove r = GameUnfinished;
13373     char *p = NULL;
13374
13375     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13376         r = gameInfo.result;
13377         p = gameInfo.resultDetails;
13378         gameInfo.resultDetails = NULL;
13379     }
13380     ClearGameInfo(&gameInfo);
13381     gameInfo.variant = v;
13382
13383     switch (gameMode) {
13384       case MachinePlaysWhite:
13385         gameInfo.event = StrSave( appData.pgnEventHeader );
13386         gameInfo.site = StrSave(HostName());
13387         gameInfo.date = PGNDate();
13388         gameInfo.round = StrSave("-");
13389         gameInfo.white = StrSave(first.tidy);
13390         gameInfo.black = StrSave(UserName());
13391         gameInfo.timeControl = TimeControlTagValue();
13392         break;
13393
13394       case MachinePlaysBlack:
13395         gameInfo.event = StrSave( appData.pgnEventHeader );
13396         gameInfo.site = StrSave(HostName());
13397         gameInfo.date = PGNDate();
13398         gameInfo.round = StrSave("-");
13399         gameInfo.white = StrSave(UserName());
13400         gameInfo.black = StrSave(first.tidy);
13401         gameInfo.timeControl = TimeControlTagValue();
13402         break;
13403
13404       case TwoMachinesPlay:
13405         gameInfo.event = StrSave( appData.pgnEventHeader );
13406         gameInfo.site = StrSave(HostName());
13407         gameInfo.date = PGNDate();
13408         if (matchGame > 0) {
13409             char buf[MSG_SIZ];
13410             snprintf(buf, MSG_SIZ, "%d", matchGame);
13411             gameInfo.round = StrSave(buf);
13412         } else {
13413             gameInfo.round = StrSave("-");
13414         }
13415         if (first.twoMachinesColor[0] == 'w') {
13416             gameInfo.white = StrSave(first.tidy);
13417             gameInfo.black = StrSave(second.tidy);
13418         } else {
13419             gameInfo.white = StrSave(second.tidy);
13420             gameInfo.black = StrSave(first.tidy);
13421         }
13422         gameInfo.timeControl = TimeControlTagValue();
13423         break;
13424
13425       case EditGame:
13426         gameInfo.event = StrSave("Edited game");
13427         gameInfo.site = StrSave(HostName());
13428         gameInfo.date = PGNDate();
13429         gameInfo.round = StrSave("-");
13430         gameInfo.white = StrSave("-");
13431         gameInfo.black = StrSave("-");
13432         gameInfo.result = r;
13433         gameInfo.resultDetails = p;
13434         break;
13435
13436       case EditPosition:
13437         gameInfo.event = StrSave("Edited position");
13438         gameInfo.site = StrSave(HostName());
13439         gameInfo.date = PGNDate();
13440         gameInfo.round = StrSave("-");
13441         gameInfo.white = StrSave("-");
13442         gameInfo.black = StrSave("-");
13443         break;
13444
13445       case IcsPlayingWhite:
13446       case IcsPlayingBlack:
13447       case IcsObserving:
13448       case IcsExamining:
13449         break;
13450
13451       case PlayFromGameFile:
13452         gameInfo.event = StrSave("Game from non-PGN file");
13453         gameInfo.site = StrSave(HostName());
13454         gameInfo.date = PGNDate();
13455         gameInfo.round = StrSave("-");
13456         gameInfo.white = StrSave("?");
13457         gameInfo.black = StrSave("?");
13458         break;
13459
13460       default:
13461         break;
13462     }
13463 }
13464
13465 void
13466 ReplaceComment(index, text)
13467      int index;
13468      char *text;
13469 {
13470     int len;
13471     char *p;
13472     float score;
13473
13474     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13475        pvInfoList[index-1].depth == len &&
13476        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13477        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13478     while (*text == '\n') text++;
13479     len = strlen(text);
13480     while (len > 0 && text[len - 1] == '\n') len--;
13481
13482     if (commentList[index] != NULL)
13483       free(commentList[index]);
13484
13485     if (len == 0) {
13486         commentList[index] = NULL;
13487         return;
13488     }
13489   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13490       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13491       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13492     commentList[index] = (char *) malloc(len + 2);
13493     strncpy(commentList[index], text, len);
13494     commentList[index][len] = '\n';
13495     commentList[index][len + 1] = NULLCHAR;
13496   } else {
13497     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13498     char *p;
13499     commentList[index] = (char *) malloc(len + 7);
13500     safeStrCpy(commentList[index], "{\n", 3);
13501     safeStrCpy(commentList[index]+2, text, len+1);
13502     commentList[index][len+2] = NULLCHAR;
13503     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13504     strcat(commentList[index], "\n}\n");
13505   }
13506 }
13507
13508 void
13509 CrushCRs(text)
13510      char *text;
13511 {
13512   char *p = text;
13513   char *q = text;
13514   char ch;
13515
13516   do {
13517     ch = *p++;
13518     if (ch == '\r') continue;
13519     *q++ = ch;
13520   } while (ch != '\0');
13521 }
13522
13523 void
13524 AppendComment(index, text, addBraces)
13525      int index;
13526      char *text;
13527      Boolean addBraces; // [HGM] braces: tells if we should add {}
13528 {
13529     int oldlen, len;
13530     char *old;
13531
13532 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13533     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13534
13535     CrushCRs(text);
13536     while (*text == '\n') text++;
13537     len = strlen(text);
13538     while (len > 0 && text[len - 1] == '\n') len--;
13539
13540     if (len == 0) return;
13541
13542     if (commentList[index] != NULL) {
13543         old = commentList[index];
13544         oldlen = strlen(old);
13545         while(commentList[index][oldlen-1] ==  '\n')
13546           commentList[index][--oldlen] = NULLCHAR;
13547         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13548         safeStrCpy(commentList[index], old, oldlen + len + 6);
13549         free(old);
13550         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13551         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13552           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13553           while (*text == '\n') { text++; len--; }
13554           commentList[index][--oldlen] = NULLCHAR;
13555       }
13556         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13557         else          strcat(commentList[index], "\n");
13558         strcat(commentList[index], text);
13559         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13560         else          strcat(commentList[index], "\n");
13561     } else {
13562         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13563         if(addBraces)
13564           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13565         else commentList[index][0] = NULLCHAR;
13566         strcat(commentList[index], text);
13567         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13568         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13569     }
13570 }
13571
13572 static char * FindStr( char * text, char * sub_text )
13573 {
13574     char * result = strstr( text, sub_text );
13575
13576     if( result != NULL ) {
13577         result += strlen( sub_text );
13578     }
13579
13580     return result;
13581 }
13582
13583 /* [AS] Try to extract PV info from PGN comment */
13584 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13585 char *GetInfoFromComment( int index, char * text )
13586 {
13587     char * sep = text, *p;
13588
13589     if( text != NULL && index > 0 ) {
13590         int score = 0;
13591         int depth = 0;
13592         int time = -1, sec = 0, deci;
13593         char * s_eval = FindStr( text, "[%eval " );
13594         char * s_emt = FindStr( text, "[%emt " );
13595
13596         if( s_eval != NULL || s_emt != NULL ) {
13597             /* New style */
13598             char delim;
13599
13600             if( s_eval != NULL ) {
13601                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13602                     return text;
13603                 }
13604
13605                 if( delim != ']' ) {
13606                     return text;
13607                 }
13608             }
13609
13610             if( s_emt != NULL ) {
13611             }
13612                 return text;
13613         }
13614         else {
13615             /* We expect something like: [+|-]nnn.nn/dd */
13616             int score_lo = 0;
13617
13618             if(*text != '{') return text; // [HGM] braces: must be normal comment
13619
13620             sep = strchr( text, '/' );
13621             if( sep == NULL || sep < (text+4) ) {
13622                 return text;
13623             }
13624
13625             p = text;
13626             if(p[1] == '(') { // comment starts with PV
13627                p = strchr(p, ')'); // locate end of PV
13628                if(p == NULL || sep < p+5) return text;
13629                // at this point we have something like "{(.*) +0.23/6 ..."
13630                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13631                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13632                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13633             }
13634             time = -1; sec = -1; deci = -1;
13635             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13636                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13637                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13638                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13639                 return text;
13640             }
13641
13642             if( score_lo < 0 || score_lo >= 100 ) {
13643                 return text;
13644             }
13645
13646             if(sec >= 0) time = 600*time + 10*sec; else
13647             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13648
13649             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13650
13651             /* [HGM] PV time: now locate end of PV info */
13652             while( *++sep >= '0' && *sep <= '9'); // strip depth
13653             if(time >= 0)
13654             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13655             if(sec >= 0)
13656             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13657             if(deci >= 0)
13658             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13659             while(*sep == ' ') sep++;
13660         }
13661
13662         if( depth <= 0 ) {
13663             return text;
13664         }
13665
13666         if( time < 0 ) {
13667             time = -1;
13668         }
13669
13670         pvInfoList[index-1].depth = depth;
13671         pvInfoList[index-1].score = score;
13672         pvInfoList[index-1].time  = 10*time; // centi-sec
13673         if(*sep == '}') *sep = 0; else *--sep = '{';
13674         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13675     }
13676     return sep;
13677 }
13678
13679 void
13680 SendToProgram(message, cps)
13681      char *message;
13682      ChessProgramState *cps;
13683 {
13684     int count, outCount, error;
13685     char buf[MSG_SIZ];
13686
13687     if (cps->pr == NULL) return;
13688     Attention(cps);
13689
13690     if (appData.debugMode) {
13691         TimeMark now;
13692         GetTimeMark(&now);
13693         fprintf(debugFP, "%ld >%-6s: %s",
13694                 SubtractTimeMarks(&now, &programStartTime),
13695                 cps->which, message);
13696     }
13697
13698     count = strlen(message);
13699     outCount = OutputToProcess(cps->pr, message, count, &error);
13700     if (outCount < count && !exiting
13701                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13702       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13703         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13704             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13705                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13706                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13707             } else {
13708                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13709             }
13710             gameInfo.resultDetails = StrSave(buf);
13711         }
13712         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13713     }
13714 }
13715
13716 void
13717 ReceiveFromProgram(isr, closure, message, count, error)
13718      InputSourceRef isr;
13719      VOIDSTAR closure;
13720      char *message;
13721      int count;
13722      int error;
13723 {
13724     char *end_str;
13725     char buf[MSG_SIZ];
13726     ChessProgramState *cps = (ChessProgramState *)closure;
13727
13728     if (isr != cps->isr) return; /* Killed intentionally */
13729     if (count <= 0) {
13730         if (count == 0) {
13731             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13732                     _(cps->which), cps->program);
13733         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13734                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13735                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13736                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13737                 } else {
13738                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13739                 }
13740                 gameInfo.resultDetails = StrSave(buf);
13741             }
13742             RemoveInputSource(cps->isr);
13743             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13744         } else {
13745             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13746                     _(cps->which), cps->program);
13747             RemoveInputSource(cps->isr);
13748
13749             /* [AS] Program is misbehaving badly... kill it */
13750             if( count == -2 ) {
13751                 DestroyChildProcess( cps->pr, 9 );
13752                 cps->pr = NoProc;
13753             }
13754
13755             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13756         }
13757         return;
13758     }
13759
13760     if ((end_str = strchr(message, '\r')) != NULL)
13761       *end_str = NULLCHAR;
13762     if ((end_str = strchr(message, '\n')) != NULL)
13763       *end_str = NULLCHAR;
13764
13765     if (appData.debugMode) {
13766         TimeMark now; int print = 1;
13767         char *quote = ""; char c; int i;
13768
13769         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13770                 char start = message[0];
13771                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13772                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13773                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13774                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13775                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13776                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13777                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13778                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13779                    sscanf(message, "hint: %c", &c)!=1 && 
13780                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13781                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13782                     print = (appData.engineComments >= 2);
13783                 }
13784                 message[0] = start; // restore original message
13785         }
13786         if(print) {
13787                 GetTimeMark(&now);
13788                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13789                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13790                         quote,
13791                         message);
13792         }
13793     }
13794
13795     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13796     if (appData.icsEngineAnalyze) {
13797         if (strstr(message, "whisper") != NULL ||
13798              strstr(message, "kibitz") != NULL ||
13799             strstr(message, "tellics") != NULL) return;
13800     }
13801
13802     HandleMachineMove(message, cps);
13803 }
13804
13805
13806 void
13807 SendTimeControl(cps, mps, tc, inc, sd, st)
13808      ChessProgramState *cps;
13809      int mps, inc, sd, st;
13810      long tc;
13811 {
13812     char buf[MSG_SIZ];
13813     int seconds;
13814
13815     if( timeControl_2 > 0 ) {
13816         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13817             tc = timeControl_2;
13818         }
13819     }
13820     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13821     inc /= cps->timeOdds;
13822     st  /= cps->timeOdds;
13823
13824     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13825
13826     if (st > 0) {
13827       /* Set exact time per move, normally using st command */
13828       if (cps->stKludge) {
13829         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13830         seconds = st % 60;
13831         if (seconds == 0) {
13832           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13833         } else {
13834           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13835         }
13836       } else {
13837         snprintf(buf, MSG_SIZ, "st %d\n", st);
13838       }
13839     } else {
13840       /* Set conventional or incremental time control, using level command */
13841       if (seconds == 0) {
13842         /* Note old gnuchess bug -- minutes:seconds used to not work.
13843            Fixed in later versions, but still avoid :seconds
13844            when seconds is 0. */
13845         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13846       } else {
13847         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13848                  seconds, inc/1000.);
13849       }
13850     }
13851     SendToProgram(buf, cps);
13852
13853     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13854     /* Orthogonally, limit search to given depth */
13855     if (sd > 0) {
13856       if (cps->sdKludge) {
13857         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13858       } else {
13859         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13860       }
13861       SendToProgram(buf, cps);
13862     }
13863
13864     if(cps->nps >= 0) { /* [HGM] nps */
13865         if(cps->supportsNPS == FALSE)
13866           cps->nps = -1; // don't use if engine explicitly says not supported!
13867         else {
13868           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13869           SendToProgram(buf, cps);
13870         }
13871     }
13872 }
13873
13874 ChessProgramState *WhitePlayer()
13875 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13876 {
13877     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13878        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13879         return &second;
13880     return &first;
13881 }
13882
13883 void
13884 SendTimeRemaining(cps, machineWhite)
13885      ChessProgramState *cps;
13886      int /*boolean*/ machineWhite;
13887 {
13888     char message[MSG_SIZ];
13889     long time, otime;
13890
13891     /* Note: this routine must be called when the clocks are stopped
13892        or when they have *just* been set or switched; otherwise
13893        it will be off by the time since the current tick started.
13894     */
13895     if (machineWhite) {
13896         time = whiteTimeRemaining / 10;
13897         otime = blackTimeRemaining / 10;
13898     } else {
13899         time = blackTimeRemaining / 10;
13900         otime = whiteTimeRemaining / 10;
13901     }
13902     /* [HGM] translate opponent's time by time-odds factor */
13903     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13904     if (appData.debugMode) {
13905         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13906     }
13907
13908     if (time <= 0) time = 1;
13909     if (otime <= 0) otime = 1;
13910
13911     snprintf(message, MSG_SIZ, "time %ld\n", time);
13912     SendToProgram(message, cps);
13913
13914     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13915     SendToProgram(message, cps);
13916 }
13917
13918 int
13919 BoolFeature(p, name, loc, cps)
13920      char **p;
13921      char *name;
13922      int *loc;
13923      ChessProgramState *cps;
13924 {
13925   char buf[MSG_SIZ];
13926   int len = strlen(name);
13927   int val;
13928
13929   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13930     (*p) += len + 1;
13931     sscanf(*p, "%d", &val);
13932     *loc = (val != 0);
13933     while (**p && **p != ' ')
13934       (*p)++;
13935     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13936     SendToProgram(buf, cps);
13937     return TRUE;
13938   }
13939   return FALSE;
13940 }
13941
13942 int
13943 IntFeature(p, name, loc, cps)
13944      char **p;
13945      char *name;
13946      int *loc;
13947      ChessProgramState *cps;
13948 {
13949   char buf[MSG_SIZ];
13950   int len = strlen(name);
13951   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13952     (*p) += len + 1;
13953     sscanf(*p, "%d", loc);
13954     while (**p && **p != ' ') (*p)++;
13955     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13956     SendToProgram(buf, cps);
13957     return TRUE;
13958   }
13959   return FALSE;
13960 }
13961
13962 int
13963 StringFeature(p, name, loc, cps)
13964      char **p;
13965      char *name;
13966      char loc[];
13967      ChessProgramState *cps;
13968 {
13969   char buf[MSG_SIZ];
13970   int len = strlen(name);
13971   if (strncmp((*p), name, len) == 0
13972       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13973     (*p) += len + 2;
13974     sscanf(*p, "%[^\"]", loc);
13975     while (**p && **p != '\"') (*p)++;
13976     if (**p == '\"') (*p)++;
13977     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13978     SendToProgram(buf, cps);
13979     return TRUE;
13980   }
13981   return FALSE;
13982 }
13983
13984 int
13985 ParseOption(Option *opt, ChessProgramState *cps)
13986 // [HGM] options: process the string that defines an engine option, and determine
13987 // name, type, default value, and allowed value range
13988 {
13989         char *p, *q, buf[MSG_SIZ];
13990         int n, min = (-1)<<31, max = 1<<31, def;
13991
13992         if(p = strstr(opt->name, " -spin ")) {
13993             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13994             if(max < min) max = min; // enforce consistency
13995             if(def < min) def = min;
13996             if(def > max) def = max;
13997             opt->value = def;
13998             opt->min = min;
13999             opt->max = max;
14000             opt->type = Spin;
14001         } else if((p = strstr(opt->name, " -slider "))) {
14002             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14003             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14004             if(max < min) max = min; // enforce consistency
14005             if(def < min) def = min;
14006             if(def > max) def = max;
14007             opt->value = def;
14008             opt->min = min;
14009             opt->max = max;
14010             opt->type = Spin; // Slider;
14011         } else if((p = strstr(opt->name, " -string "))) {
14012             opt->textValue = p+9;
14013             opt->type = TextBox;
14014         } else if((p = strstr(opt->name, " -file "))) {
14015             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14016             opt->textValue = p+7;
14017             opt->type = FileName; // FileName;
14018         } else if((p = strstr(opt->name, " -path "))) {
14019             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14020             opt->textValue = p+7;
14021             opt->type = PathName; // PathName;
14022         } else if(p = strstr(opt->name, " -check ")) {
14023             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14024             opt->value = (def != 0);
14025             opt->type = CheckBox;
14026         } else if(p = strstr(opt->name, " -combo ")) {
14027             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14028             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14029             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14030             opt->value = n = 0;
14031             while(q = StrStr(q, " /// ")) {
14032                 n++; *q = 0;    // count choices, and null-terminate each of them
14033                 q += 5;
14034                 if(*q == '*') { // remember default, which is marked with * prefix
14035                     q++;
14036                     opt->value = n;
14037                 }
14038                 cps->comboList[cps->comboCnt++] = q;
14039             }
14040             cps->comboList[cps->comboCnt++] = NULL;
14041             opt->max = n + 1;
14042             opt->type = ComboBox;
14043         } else if(p = strstr(opt->name, " -button")) {
14044             opt->type = Button;
14045         } else if(p = strstr(opt->name, " -save")) {
14046             opt->type = SaveButton;
14047         } else return FALSE;
14048         *p = 0; // terminate option name
14049         // now look if the command-line options define a setting for this engine option.
14050         if(cps->optionSettings && cps->optionSettings[0])
14051             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14052         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14053           snprintf(buf, MSG_SIZ, "option %s", p);
14054                 if(p = strstr(buf, ",")) *p = 0;
14055                 if(q = strchr(buf, '=')) switch(opt->type) {
14056                     case ComboBox:
14057                         for(n=0; n<opt->max; n++)
14058                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14059                         break;
14060                     case TextBox:
14061                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14062                         break;
14063                     case Spin:
14064                     case CheckBox:
14065                         opt->value = atoi(q+1);
14066                     default:
14067                         break;
14068                 }
14069                 strcat(buf, "\n");
14070                 SendToProgram(buf, cps);
14071         }
14072         return TRUE;
14073 }
14074
14075 void
14076 FeatureDone(cps, val)
14077      ChessProgramState* cps;
14078      int val;
14079 {
14080   DelayedEventCallback cb = GetDelayedEvent();
14081   if ((cb == InitBackEnd3 && cps == &first) ||
14082       (cb == SettingsMenuIfReady && cps == &second) ||
14083       (cb == TwoMachinesEventIfReady && cps == &second)) {
14084     CancelDelayedEvent();
14085     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14086   }
14087   cps->initDone = val;
14088 }
14089
14090 /* Parse feature command from engine */
14091 void
14092 ParseFeatures(args, cps)
14093      char* args;
14094      ChessProgramState *cps;
14095 {
14096   char *p = args;
14097   char *q;
14098   int val;
14099   char buf[MSG_SIZ];
14100
14101   for (;;) {
14102     while (*p == ' ') p++;
14103     if (*p == NULLCHAR) return;
14104
14105     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14106     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14107     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14108     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14109     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14110     if (BoolFeature(&p, "reuse", &val, cps)) {
14111       /* Engine can disable reuse, but can't enable it if user said no */
14112       if (!val) cps->reuse = FALSE;
14113       continue;
14114     }
14115     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14116     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14117       if (gameMode == TwoMachinesPlay) {
14118         DisplayTwoMachinesTitle();
14119       } else {
14120         DisplayTitle("");
14121       }
14122       continue;
14123     }
14124     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14125     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14126     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14127     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14128     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14129     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14130     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14131     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14132     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14133     if (IntFeature(&p, "done", &val, cps)) {
14134       FeatureDone(cps, val);
14135       continue;
14136     }
14137     /* Added by Tord: */
14138     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14139     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14140     /* End of additions by Tord */
14141
14142     /* [HGM] added features: */
14143     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14144     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14145     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14146     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14147     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14148     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14149     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14150         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14151           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14152             SendToProgram(buf, cps);
14153             continue;
14154         }
14155         if(cps->nrOptions >= MAX_OPTIONS) {
14156             cps->nrOptions--;
14157             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14158             DisplayError(buf, 0);
14159         }
14160         continue;
14161     }
14162     /* End of additions by HGM */
14163
14164     /* unknown feature: complain and skip */
14165     q = p;
14166     while (*q && *q != '=') q++;
14167     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14168     SendToProgram(buf, cps);
14169     p = q;
14170     if (*p == '=') {
14171       p++;
14172       if (*p == '\"') {
14173         p++;
14174         while (*p && *p != '\"') p++;
14175         if (*p == '\"') p++;
14176       } else {
14177         while (*p && *p != ' ') p++;
14178       }
14179     }
14180   }
14181
14182 }
14183
14184 void
14185 PeriodicUpdatesEvent(newState)
14186      int newState;
14187 {
14188     if (newState == appData.periodicUpdates)
14189       return;
14190
14191     appData.periodicUpdates=newState;
14192
14193     /* Display type changes, so update it now */
14194 //    DisplayAnalysis();
14195
14196     /* Get the ball rolling again... */
14197     if (newState) {
14198         AnalysisPeriodicEvent(1);
14199         StartAnalysisClock();
14200     }
14201 }
14202
14203 void
14204 PonderNextMoveEvent(newState)
14205      int newState;
14206 {
14207     if (newState == appData.ponderNextMove) return;
14208     if (gameMode == EditPosition) EditPositionDone(TRUE);
14209     if (newState) {
14210         SendToProgram("hard\n", &first);
14211         if (gameMode == TwoMachinesPlay) {
14212             SendToProgram("hard\n", &second);
14213         }
14214     } else {
14215         SendToProgram("easy\n", &first);
14216         thinkOutput[0] = NULLCHAR;
14217         if (gameMode == TwoMachinesPlay) {
14218             SendToProgram("easy\n", &second);
14219         }
14220     }
14221     appData.ponderNextMove = newState;
14222 }
14223
14224 void
14225 NewSettingEvent(option, feature, command, value)
14226      char *command;
14227      int option, value, *feature;
14228 {
14229     char buf[MSG_SIZ];
14230
14231     if (gameMode == EditPosition) EditPositionDone(TRUE);
14232     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14233     if(feature == NULL || *feature) SendToProgram(buf, &first);
14234     if (gameMode == TwoMachinesPlay) {
14235         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14236     }
14237 }
14238
14239 void
14240 ShowThinkingEvent()
14241 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14242 {
14243     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14244     int newState = appData.showThinking
14245         // [HGM] thinking: other features now need thinking output as well
14246         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14247
14248     if (oldState == newState) return;
14249     oldState = newState;
14250     if (gameMode == EditPosition) EditPositionDone(TRUE);
14251     if (oldState) {
14252         SendToProgram("post\n", &first);
14253         if (gameMode == TwoMachinesPlay) {
14254             SendToProgram("post\n", &second);
14255         }
14256     } else {
14257         SendToProgram("nopost\n", &first);
14258         thinkOutput[0] = NULLCHAR;
14259         if (gameMode == TwoMachinesPlay) {
14260             SendToProgram("nopost\n", &second);
14261         }
14262     }
14263 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14264 }
14265
14266 void
14267 AskQuestionEvent(title, question, replyPrefix, which)
14268      char *title; char *question; char *replyPrefix; char *which;
14269 {
14270   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14271   if (pr == NoProc) return;
14272   AskQuestion(title, question, replyPrefix, pr);
14273 }
14274
14275 void
14276 DisplayMove(moveNumber)
14277      int moveNumber;
14278 {
14279     char message[MSG_SIZ];
14280     char res[MSG_SIZ];
14281     char cpThinkOutput[MSG_SIZ];
14282
14283     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14284
14285     if (moveNumber == forwardMostMove - 1 ||
14286         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14287
14288         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14289
14290         if (strchr(cpThinkOutput, '\n')) {
14291             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14292         }
14293     } else {
14294         *cpThinkOutput = NULLCHAR;
14295     }
14296
14297     /* [AS] Hide thinking from human user */
14298     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14299         *cpThinkOutput = NULLCHAR;
14300         if( thinkOutput[0] != NULLCHAR ) {
14301             int i;
14302
14303             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14304                 cpThinkOutput[i] = '.';
14305             }
14306             cpThinkOutput[i] = NULLCHAR;
14307             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14308         }
14309     }
14310
14311     if (moveNumber == forwardMostMove - 1 &&
14312         gameInfo.resultDetails != NULL) {
14313         if (gameInfo.resultDetails[0] == NULLCHAR) {
14314           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14315         } else {
14316           snprintf(res, MSG_SIZ, " {%s} %s",
14317                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14318         }
14319     } else {
14320         res[0] = NULLCHAR;
14321     }
14322
14323     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14324         DisplayMessage(res, cpThinkOutput);
14325     } else {
14326       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14327                 WhiteOnMove(moveNumber) ? " " : ".. ",
14328                 parseList[moveNumber], res);
14329         DisplayMessage(message, cpThinkOutput);
14330     }
14331 }
14332
14333 void
14334 DisplayComment(moveNumber, text)
14335      int moveNumber;
14336      char *text;
14337 {
14338     char title[MSG_SIZ];
14339     char buf[8000]; // comment can be long!
14340     int score, depth;
14341
14342     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14343       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14344     } else {
14345       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14346               WhiteOnMove(moveNumber) ? " " : ".. ",
14347               parseList[moveNumber]);
14348     }
14349     // [HGM] PV info: display PV info together with (or as) comment
14350     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14351       if(text == NULL) text = "";
14352       score = pvInfoList[moveNumber].score;
14353       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14354               depth, (pvInfoList[moveNumber].time+50)/100, text);
14355       text = buf;
14356     }
14357     if (text != NULL && (appData.autoDisplayComment || commentUp))
14358         CommentPopUp(title, text);
14359 }
14360
14361 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14362  * might be busy thinking or pondering.  It can be omitted if your
14363  * gnuchess is configured to stop thinking immediately on any user
14364  * input.  However, that gnuchess feature depends on the FIONREAD
14365  * ioctl, which does not work properly on some flavors of Unix.
14366  */
14367 void
14368 Attention(cps)
14369      ChessProgramState *cps;
14370 {
14371 #if ATTENTION
14372     if (!cps->useSigint) return;
14373     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14374     switch (gameMode) {
14375       case MachinePlaysWhite:
14376       case MachinePlaysBlack:
14377       case TwoMachinesPlay:
14378       case IcsPlayingWhite:
14379       case IcsPlayingBlack:
14380       case AnalyzeMode:
14381       case AnalyzeFile:
14382         /* Skip if we know it isn't thinking */
14383         if (!cps->maybeThinking) return;
14384         if (appData.debugMode)
14385           fprintf(debugFP, "Interrupting %s\n", cps->which);
14386         InterruptChildProcess(cps->pr);
14387         cps->maybeThinking = FALSE;
14388         break;
14389       default:
14390         break;
14391     }
14392 #endif /*ATTENTION*/
14393 }
14394
14395 int
14396 CheckFlags()
14397 {
14398     if (whiteTimeRemaining <= 0) {
14399         if (!whiteFlag) {
14400             whiteFlag = TRUE;
14401             if (appData.icsActive) {
14402                 if (appData.autoCallFlag &&
14403                     gameMode == IcsPlayingBlack && !blackFlag) {
14404                   SendToICS(ics_prefix);
14405                   SendToICS("flag\n");
14406                 }
14407             } else {
14408                 if (blackFlag) {
14409                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14410                 } else {
14411                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14412                     if (appData.autoCallFlag) {
14413                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14414                         return TRUE;
14415                     }
14416                 }
14417             }
14418         }
14419     }
14420     if (blackTimeRemaining <= 0) {
14421         if (!blackFlag) {
14422             blackFlag = TRUE;
14423             if (appData.icsActive) {
14424                 if (appData.autoCallFlag &&
14425                     gameMode == IcsPlayingWhite && !whiteFlag) {
14426                   SendToICS(ics_prefix);
14427                   SendToICS("flag\n");
14428                 }
14429             } else {
14430                 if (whiteFlag) {
14431                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14432                 } else {
14433                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14434                     if (appData.autoCallFlag) {
14435                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14436                         return TRUE;
14437                     }
14438                 }
14439             }
14440         }
14441     }
14442     return FALSE;
14443 }
14444
14445 void
14446 CheckTimeControl()
14447 {
14448     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14449         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14450
14451     /*
14452      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14453      */
14454     if ( !WhiteOnMove(forwardMostMove) ) {
14455         /* White made time control */
14456         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14457         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14458         /* [HGM] time odds: correct new time quota for time odds! */
14459                                             / WhitePlayer()->timeOdds;
14460         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14461     } else {
14462         lastBlack -= blackTimeRemaining;
14463         /* Black made time control */
14464         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14465                                             / WhitePlayer()->other->timeOdds;
14466         lastWhite = whiteTimeRemaining;
14467     }
14468 }
14469
14470 void
14471 DisplayBothClocks()
14472 {
14473     int wom = gameMode == EditPosition ?
14474       !blackPlaysFirst : WhiteOnMove(currentMove);
14475     DisplayWhiteClock(whiteTimeRemaining, wom);
14476     DisplayBlackClock(blackTimeRemaining, !wom);
14477 }
14478
14479
14480 /* Timekeeping seems to be a portability nightmare.  I think everyone
14481    has ftime(), but I'm really not sure, so I'm including some ifdefs
14482    to use other calls if you don't.  Clocks will be less accurate if
14483    you have neither ftime nor gettimeofday.
14484 */
14485
14486 /* VS 2008 requires the #include outside of the function */
14487 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14488 #include <sys/timeb.h>
14489 #endif
14490
14491 /* Get the current time as a TimeMark */
14492 void
14493 GetTimeMark(tm)
14494      TimeMark *tm;
14495 {
14496 #if HAVE_GETTIMEOFDAY
14497
14498     struct timeval timeVal;
14499     struct timezone timeZone;
14500
14501     gettimeofday(&timeVal, &timeZone);
14502     tm->sec = (long) timeVal.tv_sec;
14503     tm->ms = (int) (timeVal.tv_usec / 1000L);
14504
14505 #else /*!HAVE_GETTIMEOFDAY*/
14506 #if HAVE_FTIME
14507
14508 // include <sys/timeb.h> / moved to just above start of function
14509     struct timeb timeB;
14510
14511     ftime(&timeB);
14512     tm->sec = (long) timeB.time;
14513     tm->ms = (int) timeB.millitm;
14514
14515 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14516     tm->sec = (long) time(NULL);
14517     tm->ms = 0;
14518 #endif
14519 #endif
14520 }
14521
14522 /* Return the difference in milliseconds between two
14523    time marks.  We assume the difference will fit in a long!
14524 */
14525 long
14526 SubtractTimeMarks(tm2, tm1)
14527      TimeMark *tm2, *tm1;
14528 {
14529     return 1000L*(tm2->sec - tm1->sec) +
14530            (long) (tm2->ms - tm1->ms);
14531 }
14532
14533
14534 /*
14535  * Code to manage the game clocks.
14536  *
14537  * In tournament play, black starts the clock and then white makes a move.
14538  * We give the human user a slight advantage if he is playing white---the
14539  * clocks don't run until he makes his first move, so it takes zero time.
14540  * Also, we don't account for network lag, so we could get out of sync
14541  * with GNU Chess's clock -- but then, referees are always right.
14542  */
14543
14544 static TimeMark tickStartTM;
14545 static long intendedTickLength;
14546
14547 long
14548 NextTickLength(timeRemaining)
14549      long timeRemaining;
14550 {
14551     long nominalTickLength, nextTickLength;
14552
14553     if (timeRemaining > 0L && timeRemaining <= 10000L)
14554       nominalTickLength = 100L;
14555     else
14556       nominalTickLength = 1000L;
14557     nextTickLength = timeRemaining % nominalTickLength;
14558     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14559
14560     return nextTickLength;
14561 }
14562
14563 /* Adjust clock one minute up or down */
14564 void
14565 AdjustClock(Boolean which, int dir)
14566 {
14567     if(which) blackTimeRemaining += 60000*dir;
14568     else      whiteTimeRemaining += 60000*dir;
14569     DisplayBothClocks();
14570 }
14571
14572 /* Stop clocks and reset to a fresh time control */
14573 void
14574 ResetClocks()
14575 {
14576     (void) StopClockTimer();
14577     if (appData.icsActive) {
14578         whiteTimeRemaining = blackTimeRemaining = 0;
14579     } else if (searchTime) {
14580         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14581         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14582     } else { /* [HGM] correct new time quote for time odds */
14583         whiteTC = blackTC = fullTimeControlString;
14584         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14585         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14586     }
14587     if (whiteFlag || blackFlag) {
14588         DisplayTitle("");
14589         whiteFlag = blackFlag = FALSE;
14590     }
14591     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14592     DisplayBothClocks();
14593 }
14594
14595 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14596
14597 /* Decrement running clock by amount of time that has passed */
14598 void
14599 DecrementClocks()
14600 {
14601     long timeRemaining;
14602     long lastTickLength, fudge;
14603     TimeMark now;
14604
14605     if (!appData.clockMode) return;
14606     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14607
14608     GetTimeMark(&now);
14609
14610     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14611
14612     /* Fudge if we woke up a little too soon */
14613     fudge = intendedTickLength - lastTickLength;
14614     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14615
14616     if (WhiteOnMove(forwardMostMove)) {
14617         if(whiteNPS >= 0) lastTickLength = 0;
14618         timeRemaining = whiteTimeRemaining -= lastTickLength;
14619         if(timeRemaining < 0 && !appData.icsActive) {
14620             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14621             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14622                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14623                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14624             }
14625         }
14626         DisplayWhiteClock(whiteTimeRemaining - fudge,
14627                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14628     } else {
14629         if(blackNPS >= 0) lastTickLength = 0;
14630         timeRemaining = blackTimeRemaining -= lastTickLength;
14631         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14632             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14633             if(suddenDeath) {
14634                 blackStartMove = forwardMostMove;
14635                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14636             }
14637         }
14638         DisplayBlackClock(blackTimeRemaining - fudge,
14639                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14640     }
14641     if (CheckFlags()) return;
14642
14643     tickStartTM = now;
14644     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14645     StartClockTimer(intendedTickLength);
14646
14647     /* if the time remaining has fallen below the alarm threshold, sound the
14648      * alarm. if the alarm has sounded and (due to a takeback or time control
14649      * with increment) the time remaining has increased to a level above the
14650      * threshold, reset the alarm so it can sound again.
14651      */
14652
14653     if (appData.icsActive && appData.icsAlarm) {
14654
14655         /* make sure we are dealing with the user's clock */
14656         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14657                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14658            )) return;
14659
14660         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14661             alarmSounded = FALSE;
14662         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14663             PlayAlarmSound();
14664             alarmSounded = TRUE;
14665         }
14666     }
14667 }
14668
14669
14670 /* A player has just moved, so stop the previously running
14671    clock and (if in clock mode) start the other one.
14672    We redisplay both clocks in case we're in ICS mode, because
14673    ICS gives us an update to both clocks after every move.
14674    Note that this routine is called *after* forwardMostMove
14675    is updated, so the last fractional tick must be subtracted
14676    from the color that is *not* on move now.
14677 */
14678 void
14679 SwitchClocks(int newMoveNr)
14680 {
14681     long lastTickLength;
14682     TimeMark now;
14683     int flagged = FALSE;
14684
14685     GetTimeMark(&now);
14686
14687     if (StopClockTimer() && appData.clockMode) {
14688         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14689         if (!WhiteOnMove(forwardMostMove)) {
14690             if(blackNPS >= 0) lastTickLength = 0;
14691             blackTimeRemaining -= lastTickLength;
14692            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14693 //         if(pvInfoList[forwardMostMove].time == -1)
14694                  pvInfoList[forwardMostMove].time =               // use GUI time
14695                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14696         } else {
14697            if(whiteNPS >= 0) lastTickLength = 0;
14698            whiteTimeRemaining -= lastTickLength;
14699            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14700 //         if(pvInfoList[forwardMostMove].time == -1)
14701                  pvInfoList[forwardMostMove].time =
14702                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14703         }
14704         flagged = CheckFlags();
14705     }
14706     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14707     CheckTimeControl();
14708
14709     if (flagged || !appData.clockMode) return;
14710
14711     switch (gameMode) {
14712       case MachinePlaysBlack:
14713       case MachinePlaysWhite:
14714       case BeginningOfGame:
14715         if (pausing) return;
14716         break;
14717
14718       case EditGame:
14719       case PlayFromGameFile:
14720       case IcsExamining:
14721         return;
14722
14723       default:
14724         break;
14725     }
14726
14727     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14728         if(WhiteOnMove(forwardMostMove))
14729              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14730         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14731     }
14732
14733     tickStartTM = now;
14734     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14735       whiteTimeRemaining : blackTimeRemaining);
14736     StartClockTimer(intendedTickLength);
14737 }
14738
14739
14740 /* Stop both clocks */
14741 void
14742 StopClocks()
14743 {
14744     long lastTickLength;
14745     TimeMark now;
14746
14747     if (!StopClockTimer()) return;
14748     if (!appData.clockMode) return;
14749
14750     GetTimeMark(&now);
14751
14752     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14753     if (WhiteOnMove(forwardMostMove)) {
14754         if(whiteNPS >= 0) lastTickLength = 0;
14755         whiteTimeRemaining -= lastTickLength;
14756         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14757     } else {
14758         if(blackNPS >= 0) lastTickLength = 0;
14759         blackTimeRemaining -= lastTickLength;
14760         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14761     }
14762     CheckFlags();
14763 }
14764
14765 /* Start clock of player on move.  Time may have been reset, so
14766    if clock is already running, stop and restart it. */
14767 void
14768 StartClocks()
14769 {
14770     (void) StopClockTimer(); /* in case it was running already */
14771     DisplayBothClocks();
14772     if (CheckFlags()) return;
14773
14774     if (!appData.clockMode) return;
14775     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14776
14777     GetTimeMark(&tickStartTM);
14778     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14779       whiteTimeRemaining : blackTimeRemaining);
14780
14781    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14782     whiteNPS = blackNPS = -1;
14783     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14784        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14785         whiteNPS = first.nps;
14786     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14787        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14788         blackNPS = first.nps;
14789     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14790         whiteNPS = second.nps;
14791     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14792         blackNPS = second.nps;
14793     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14794
14795     StartClockTimer(intendedTickLength);
14796 }
14797
14798 char *
14799 TimeString(ms)
14800      long ms;
14801 {
14802     long second, minute, hour, day;
14803     char *sign = "";
14804     static char buf[32];
14805
14806     if (ms > 0 && ms <= 9900) {
14807       /* convert milliseconds to tenths, rounding up */
14808       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14809
14810       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14811       return buf;
14812     }
14813
14814     /* convert milliseconds to seconds, rounding up */
14815     /* use floating point to avoid strangeness of integer division
14816        with negative dividends on many machines */
14817     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14818
14819     if (second < 0) {
14820         sign = "-";
14821         second = -second;
14822     }
14823
14824     day = second / (60 * 60 * 24);
14825     second = second % (60 * 60 * 24);
14826     hour = second / (60 * 60);
14827     second = second % (60 * 60);
14828     minute = second / 60;
14829     second = second % 60;
14830
14831     if (day > 0)
14832       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14833               sign, day, hour, minute, second);
14834     else if (hour > 0)
14835       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14836     else
14837       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14838
14839     return buf;
14840 }
14841
14842
14843 /*
14844  * This is necessary because some C libraries aren't ANSI C compliant yet.
14845  */
14846 char *
14847 StrStr(string, match)
14848      char *string, *match;
14849 {
14850     int i, length;
14851
14852     length = strlen(match);
14853
14854     for (i = strlen(string) - length; i >= 0; i--, string++)
14855       if (!strncmp(match, string, length))
14856         return string;
14857
14858     return NULL;
14859 }
14860
14861 char *
14862 StrCaseStr(string, match)
14863      char *string, *match;
14864 {
14865     int i, j, length;
14866
14867     length = strlen(match);
14868
14869     for (i = strlen(string) - length; i >= 0; i--, string++) {
14870         for (j = 0; j < length; j++) {
14871             if (ToLower(match[j]) != ToLower(string[j]))
14872               break;
14873         }
14874         if (j == length) return string;
14875     }
14876
14877     return NULL;
14878 }
14879
14880 #ifndef _amigados
14881 int
14882 StrCaseCmp(s1, s2)
14883      char *s1, *s2;
14884 {
14885     char c1, c2;
14886
14887     for (;;) {
14888         c1 = ToLower(*s1++);
14889         c2 = ToLower(*s2++);
14890         if (c1 > c2) return 1;
14891         if (c1 < c2) return -1;
14892         if (c1 == NULLCHAR) return 0;
14893     }
14894 }
14895
14896
14897 int
14898 ToLower(c)
14899      int c;
14900 {
14901     return isupper(c) ? tolower(c) : c;
14902 }
14903
14904
14905 int
14906 ToUpper(c)
14907      int c;
14908 {
14909     return islower(c) ? toupper(c) : c;
14910 }
14911 #endif /* !_amigados    */
14912
14913 char *
14914 StrSave(s)
14915      char *s;
14916 {
14917   char *ret;
14918
14919   if ((ret = (char *) malloc(strlen(s) + 1)))
14920     {
14921       safeStrCpy(ret, s, strlen(s)+1);
14922     }
14923   return ret;
14924 }
14925
14926 char *
14927 StrSavePtr(s, savePtr)
14928      char *s, **savePtr;
14929 {
14930     if (*savePtr) {
14931         free(*savePtr);
14932     }
14933     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14934       safeStrCpy(*savePtr, s, strlen(s)+1);
14935     }
14936     return(*savePtr);
14937 }
14938
14939 char *
14940 PGNDate()
14941 {
14942     time_t clock;
14943     struct tm *tm;
14944     char buf[MSG_SIZ];
14945
14946     clock = time((time_t *)NULL);
14947     tm = localtime(&clock);
14948     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14949             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14950     return StrSave(buf);
14951 }
14952
14953
14954 char *
14955 PositionToFEN(move, overrideCastling)
14956      int move;
14957      char *overrideCastling;
14958 {
14959     int i, j, fromX, fromY, toX, toY;
14960     int whiteToPlay;
14961     char buf[128];
14962     char *p, *q;
14963     int emptycount;
14964     ChessSquare piece;
14965
14966     whiteToPlay = (gameMode == EditPosition) ?
14967       !blackPlaysFirst : (move % 2 == 0);
14968     p = buf;
14969
14970     /* Piece placement data */
14971     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14972         emptycount = 0;
14973         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14974             if (boards[move][i][j] == EmptySquare) {
14975                 emptycount++;
14976             } else { ChessSquare piece = boards[move][i][j];
14977                 if (emptycount > 0) {
14978                     if(emptycount<10) /* [HGM] can be >= 10 */
14979                         *p++ = '0' + emptycount;
14980                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14981                     emptycount = 0;
14982                 }
14983                 if(PieceToChar(piece) == '+') {
14984                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14985                     *p++ = '+';
14986                     piece = (ChessSquare)(DEMOTED piece);
14987                 }
14988                 *p++ = PieceToChar(piece);
14989                 if(p[-1] == '~') {
14990                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14991                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14992                     *p++ = '~';
14993                 }
14994             }
14995         }
14996         if (emptycount > 0) {
14997             if(emptycount<10) /* [HGM] can be >= 10 */
14998                 *p++ = '0' + emptycount;
14999             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15000             emptycount = 0;
15001         }
15002         *p++ = '/';
15003     }
15004     *(p - 1) = ' ';
15005
15006     /* [HGM] print Crazyhouse or Shogi holdings */
15007     if( gameInfo.holdingsWidth ) {
15008         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15009         q = p;
15010         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15011             piece = boards[move][i][BOARD_WIDTH-1];
15012             if( piece != EmptySquare )
15013               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15014                   *p++ = PieceToChar(piece);
15015         }
15016         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15017             piece = boards[move][BOARD_HEIGHT-i-1][0];
15018             if( piece != EmptySquare )
15019               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15020                   *p++ = PieceToChar(piece);
15021         }
15022
15023         if( q == p ) *p++ = '-';
15024         *p++ = ']';
15025         *p++ = ' ';
15026     }
15027
15028     /* Active color */
15029     *p++ = whiteToPlay ? 'w' : 'b';
15030     *p++ = ' ';
15031
15032   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15033     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15034   } else {
15035   if(nrCastlingRights) {
15036      q = p;
15037      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15038        /* [HGM] write directly from rights */
15039            if(boards[move][CASTLING][2] != NoRights &&
15040               boards[move][CASTLING][0] != NoRights   )
15041                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15042            if(boards[move][CASTLING][2] != NoRights &&
15043               boards[move][CASTLING][1] != NoRights   )
15044                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15045            if(boards[move][CASTLING][5] != NoRights &&
15046               boards[move][CASTLING][3] != NoRights   )
15047                 *p++ = boards[move][CASTLING][3] + AAA;
15048            if(boards[move][CASTLING][5] != NoRights &&
15049               boards[move][CASTLING][4] != NoRights   )
15050                 *p++ = boards[move][CASTLING][4] + AAA;
15051      } else {
15052
15053         /* [HGM] write true castling rights */
15054         if( nrCastlingRights == 6 ) {
15055             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15056                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15057             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15058                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15059             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15060                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15061             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15062                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15063         }
15064      }
15065      if (q == p) *p++ = '-'; /* No castling rights */
15066      *p++ = ' ';
15067   }
15068
15069   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15070      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15071     /* En passant target square */
15072     if (move > backwardMostMove) {
15073         fromX = moveList[move - 1][0] - AAA;
15074         fromY = moveList[move - 1][1] - ONE;
15075         toX = moveList[move - 1][2] - AAA;
15076         toY = moveList[move - 1][3] - ONE;
15077         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15078             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15079             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15080             fromX == toX) {
15081             /* 2-square pawn move just happened */
15082             *p++ = toX + AAA;
15083             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15084         } else {
15085             *p++ = '-';
15086         }
15087     } else if(move == backwardMostMove) {
15088         // [HGM] perhaps we should always do it like this, and forget the above?
15089         if((signed char)boards[move][EP_STATUS] >= 0) {
15090             *p++ = boards[move][EP_STATUS] + AAA;
15091             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15092         } else {
15093             *p++ = '-';
15094         }
15095     } else {
15096         *p++ = '-';
15097     }
15098     *p++ = ' ';
15099   }
15100   }
15101
15102     /* [HGM] find reversible plies */
15103     {   int i = 0, j=move;
15104
15105         if (appData.debugMode) { int k;
15106             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15107             for(k=backwardMostMove; k<=forwardMostMove; k++)
15108                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15109
15110         }
15111
15112         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15113         if( j == backwardMostMove ) i += initialRulePlies;
15114         sprintf(p, "%d ", i);
15115         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15116     }
15117     /* Fullmove number */
15118     sprintf(p, "%d", (move / 2) + 1);
15119
15120     return StrSave(buf);
15121 }
15122
15123 Boolean
15124 ParseFEN(board, blackPlaysFirst, fen)
15125     Board board;
15126      int *blackPlaysFirst;
15127      char *fen;
15128 {
15129     int i, j;
15130     char *p, c;
15131     int emptycount;
15132     ChessSquare piece;
15133
15134     p = fen;
15135
15136     /* [HGM] by default clear Crazyhouse holdings, if present */
15137     if(gameInfo.holdingsWidth) {
15138        for(i=0; i<BOARD_HEIGHT; i++) {
15139            board[i][0]             = EmptySquare; /* black holdings */
15140            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15141            board[i][1]             = (ChessSquare) 0; /* black counts */
15142            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15143        }
15144     }
15145
15146     /* Piece placement data */
15147     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15148         j = 0;
15149         for (;;) {
15150             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15151                 if (*p == '/') p++;
15152                 emptycount = gameInfo.boardWidth - j;
15153                 while (emptycount--)
15154                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15155                 break;
15156 #if(BOARD_FILES >= 10)
15157             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15158                 p++; emptycount=10;
15159                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15160                 while (emptycount--)
15161                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15162 #endif
15163             } else if (isdigit(*p)) {
15164                 emptycount = *p++ - '0';
15165                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15166                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15167                 while (emptycount--)
15168                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15169             } else if (*p == '+' || isalpha(*p)) {
15170                 if (j >= gameInfo.boardWidth) return FALSE;
15171                 if(*p=='+') {
15172                     piece = CharToPiece(*++p);
15173                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15174                     piece = (ChessSquare) (PROMOTED piece ); p++;
15175                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15176                 } else piece = CharToPiece(*p++);
15177
15178                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15179                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15180                     piece = (ChessSquare) (PROMOTED piece);
15181                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15182                     p++;
15183                 }
15184                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15185             } else {
15186                 return FALSE;
15187             }
15188         }
15189     }
15190     while (*p == '/' || *p == ' ') p++;
15191
15192     /* [HGM] look for Crazyhouse holdings here */
15193     while(*p==' ') p++;
15194     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15195         if(*p == '[') p++;
15196         if(*p == '-' ) p++; /* empty holdings */ else {
15197             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15198             /* if we would allow FEN reading to set board size, we would   */
15199             /* have to add holdings and shift the board read so far here   */
15200             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15201                 p++;
15202                 if((int) piece >= (int) BlackPawn ) {
15203                     i = (int)piece - (int)BlackPawn;
15204                     i = PieceToNumber((ChessSquare)i);
15205                     if( i >= gameInfo.holdingsSize ) return FALSE;
15206                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15207                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15208                 } else {
15209                     i = (int)piece - (int)WhitePawn;
15210                     i = PieceToNumber((ChessSquare)i);
15211                     if( i >= gameInfo.holdingsSize ) return FALSE;
15212                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15213                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15214                 }
15215             }
15216         }
15217         if(*p == ']') p++;
15218     }
15219
15220     while(*p == ' ') p++;
15221
15222     /* Active color */
15223     c = *p++;
15224     if(appData.colorNickNames) {
15225       if( c == appData.colorNickNames[0] ) c = 'w'; else
15226       if( c == appData.colorNickNames[1] ) c = 'b';
15227     }
15228     switch (c) {
15229       case 'w':
15230         *blackPlaysFirst = FALSE;
15231         break;
15232       case 'b':
15233         *blackPlaysFirst = TRUE;
15234         break;
15235       default:
15236         return FALSE;
15237     }
15238
15239     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15240     /* return the extra info in global variiables             */
15241
15242     /* set defaults in case FEN is incomplete */
15243     board[EP_STATUS] = EP_UNKNOWN;
15244     for(i=0; i<nrCastlingRights; i++ ) {
15245         board[CASTLING][i] =
15246             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15247     }   /* assume possible unless obviously impossible */
15248     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15249     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15250     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15251                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15252     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15253     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15254     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15255                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15256     FENrulePlies = 0;
15257
15258     while(*p==' ') p++;
15259     if(nrCastlingRights) {
15260       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15261           /* castling indicator present, so default becomes no castlings */
15262           for(i=0; i<nrCastlingRights; i++ ) {
15263                  board[CASTLING][i] = NoRights;
15264           }
15265       }
15266       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15267              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15268              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15269              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15270         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15271
15272         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15273             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15274             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15275         }
15276         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15277             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15278         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15279                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15280         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15281                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15282         switch(c) {
15283           case'K':
15284               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15285               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15286               board[CASTLING][2] = whiteKingFile;
15287               break;
15288           case'Q':
15289               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15290               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15291               board[CASTLING][2] = whiteKingFile;
15292               break;
15293           case'k':
15294               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15295               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15296               board[CASTLING][5] = blackKingFile;
15297               break;
15298           case'q':
15299               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15300               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15301               board[CASTLING][5] = blackKingFile;
15302           case '-':
15303               break;
15304           default: /* FRC castlings */
15305               if(c >= 'a') { /* black rights */
15306                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15307                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15308                   if(i == BOARD_RGHT) break;
15309                   board[CASTLING][5] = i;
15310                   c -= AAA;
15311                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15312                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15313                   if(c > i)
15314                       board[CASTLING][3] = c;
15315                   else
15316                       board[CASTLING][4] = c;
15317               } else { /* white rights */
15318                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15319                     if(board[0][i] == WhiteKing) break;
15320                   if(i == BOARD_RGHT) break;
15321                   board[CASTLING][2] = i;
15322                   c -= AAA - 'a' + 'A';
15323                   if(board[0][c] >= WhiteKing) break;
15324                   if(c > i)
15325                       board[CASTLING][0] = c;
15326                   else
15327                       board[CASTLING][1] = c;
15328               }
15329         }
15330       }
15331       for(i=0; i<nrCastlingRights; i++)
15332         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15333     if (appData.debugMode) {
15334         fprintf(debugFP, "FEN castling rights:");
15335         for(i=0; i<nrCastlingRights; i++)
15336         fprintf(debugFP, " %d", board[CASTLING][i]);
15337         fprintf(debugFP, "\n");
15338     }
15339
15340       while(*p==' ') p++;
15341     }
15342
15343     /* read e.p. field in games that know e.p. capture */
15344     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15345        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15346       if(*p=='-') {
15347         p++; board[EP_STATUS] = EP_NONE;
15348       } else {
15349          char c = *p++ - AAA;
15350
15351          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15352          if(*p >= '0' && *p <='9') p++;
15353          board[EP_STATUS] = c;
15354       }
15355     }
15356
15357
15358     if(sscanf(p, "%d", &i) == 1) {
15359         FENrulePlies = i; /* 50-move ply counter */
15360         /* (The move number is still ignored)    */
15361     }
15362
15363     return TRUE;
15364 }
15365
15366 void
15367 EditPositionPasteFEN(char *fen)
15368 {
15369   if (fen != NULL) {
15370     Board initial_position;
15371
15372     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15373       DisplayError(_("Bad FEN position in clipboard"), 0);
15374       return ;
15375     } else {
15376       int savedBlackPlaysFirst = blackPlaysFirst;
15377       EditPositionEvent();
15378       blackPlaysFirst = savedBlackPlaysFirst;
15379       CopyBoard(boards[0], initial_position);
15380       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15381       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15382       DisplayBothClocks();
15383       DrawPosition(FALSE, boards[currentMove]);
15384     }
15385   }
15386 }
15387
15388 static char cseq[12] = "\\   ";
15389
15390 Boolean set_cont_sequence(char *new_seq)
15391 {
15392     int len;
15393     Boolean ret;
15394
15395     // handle bad attempts to set the sequence
15396         if (!new_seq)
15397                 return 0; // acceptable error - no debug
15398
15399     len = strlen(new_seq);
15400     ret = (len > 0) && (len < sizeof(cseq));
15401     if (ret)
15402       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15403     else if (appData.debugMode)
15404       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15405     return ret;
15406 }
15407
15408 /*
15409     reformat a source message so words don't cross the width boundary.  internal
15410     newlines are not removed.  returns the wrapped size (no null character unless
15411     included in source message).  If dest is NULL, only calculate the size required
15412     for the dest buffer.  lp argument indicats line position upon entry, and it's
15413     passed back upon exit.
15414 */
15415 int wrap(char *dest, char *src, int count, int width, int *lp)
15416 {
15417     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15418
15419     cseq_len = strlen(cseq);
15420     old_line = line = *lp;
15421     ansi = len = clen = 0;
15422
15423     for (i=0; i < count; i++)
15424     {
15425         if (src[i] == '\033')
15426             ansi = 1;
15427
15428         // if we hit the width, back up
15429         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15430         {
15431             // store i & len in case the word is too long
15432             old_i = i, old_len = len;
15433
15434             // find the end of the last word
15435             while (i && src[i] != ' ' && src[i] != '\n')
15436             {
15437                 i--;
15438                 len--;
15439             }
15440
15441             // word too long?  restore i & len before splitting it
15442             if ((old_i-i+clen) >= width)
15443             {
15444                 i = old_i;
15445                 len = old_len;
15446             }
15447
15448             // extra space?
15449             if (i && src[i-1] == ' ')
15450                 len--;
15451
15452             if (src[i] != ' ' && src[i] != '\n')
15453             {
15454                 i--;
15455                 if (len)
15456                     len--;
15457             }
15458
15459             // now append the newline and continuation sequence
15460             if (dest)
15461                 dest[len] = '\n';
15462             len++;
15463             if (dest)
15464                 strncpy(dest+len, cseq, cseq_len);
15465             len += cseq_len;
15466             line = cseq_len;
15467             clen = cseq_len;
15468             continue;
15469         }
15470
15471         if (dest)
15472             dest[len] = src[i];
15473         len++;
15474         if (!ansi)
15475             line++;
15476         if (src[i] == '\n')
15477             line = 0;
15478         if (src[i] == 'm')
15479             ansi = 0;
15480     }
15481     if (dest && appData.debugMode)
15482     {
15483         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15484             count, width, line, len, *lp);
15485         show_bytes(debugFP, src, count);
15486         fprintf(debugFP, "\ndest: ");
15487         show_bytes(debugFP, dest, len);
15488         fprintf(debugFP, "\n");
15489     }
15490     *lp = dest ? line : old_line;
15491
15492     return len;
15493 }
15494
15495 // [HGM] vari: routines for shelving variations
15496
15497 void
15498 PushTail(int firstMove, int lastMove)
15499 {
15500         int i, j, nrMoves = lastMove - firstMove;
15501
15502         if(appData.icsActive) { // only in local mode
15503                 forwardMostMove = currentMove; // mimic old ICS behavior
15504                 return;
15505         }
15506         if(storedGames >= MAX_VARIATIONS-1) return;
15507
15508         // push current tail of game on stack
15509         savedResult[storedGames] = gameInfo.result;
15510         savedDetails[storedGames] = gameInfo.resultDetails;
15511         gameInfo.resultDetails = NULL;
15512         savedFirst[storedGames] = firstMove;
15513         savedLast [storedGames] = lastMove;
15514         savedFramePtr[storedGames] = framePtr;
15515         framePtr -= nrMoves; // reserve space for the boards
15516         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15517             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15518             for(j=0; j<MOVE_LEN; j++)
15519                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15520             for(j=0; j<2*MOVE_LEN; j++)
15521                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15522             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15523             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15524             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15525             pvInfoList[firstMove+i-1].depth = 0;
15526             commentList[framePtr+i] = commentList[firstMove+i];
15527             commentList[firstMove+i] = NULL;
15528         }
15529
15530         storedGames++;
15531         forwardMostMove = firstMove; // truncate game so we can start variation
15532         if(storedGames == 1) GreyRevert(FALSE);
15533 }
15534
15535 Boolean
15536 PopTail(Boolean annotate)
15537 {
15538         int i, j, nrMoves;
15539         char buf[8000], moveBuf[20];
15540
15541         if(appData.icsActive) return FALSE; // only in local mode
15542         if(!storedGames) return FALSE; // sanity
15543         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15544
15545         storedGames--;
15546         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15547         nrMoves = savedLast[storedGames] - currentMove;
15548         if(annotate) {
15549                 int cnt = 10;
15550                 if(!WhiteOnMove(currentMove))
15551                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15552                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15553                 for(i=currentMove; i<forwardMostMove; i++) {
15554                         if(WhiteOnMove(i))
15555                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15556                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15557                         strcat(buf, moveBuf);
15558                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15559                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15560                 }
15561                 strcat(buf, ")");
15562         }
15563         for(i=1; i<=nrMoves; i++) { // copy last variation back
15564             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15565             for(j=0; j<MOVE_LEN; j++)
15566                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15567             for(j=0; j<2*MOVE_LEN; j++)
15568                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15569             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15570             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15571             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15572             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15573             commentList[currentMove+i] = commentList[framePtr+i];
15574             commentList[framePtr+i] = NULL;
15575         }
15576         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15577         framePtr = savedFramePtr[storedGames];
15578         gameInfo.result = savedResult[storedGames];
15579         if(gameInfo.resultDetails != NULL) {
15580             free(gameInfo.resultDetails);
15581       }
15582         gameInfo.resultDetails = savedDetails[storedGames];
15583         forwardMostMove = currentMove + nrMoves;
15584         if(storedGames == 0) GreyRevert(TRUE);
15585         return TRUE;
15586 }
15587
15588 void
15589 CleanupTail()
15590 {       // remove all shelved variations
15591         int i;
15592         for(i=0; i<storedGames; i++) {
15593             if(savedDetails[i])
15594                 free(savedDetails[i]);
15595             savedDetails[i] = NULL;
15596         }
15597         for(i=framePtr; i<MAX_MOVES; i++) {
15598                 if(commentList[i]) free(commentList[i]);
15599                 commentList[i] = NULL;
15600         }
15601         framePtr = MAX_MOVES-1;
15602         storedGames = 0;
15603 }
15604
15605 void
15606 LoadVariation(int index, char *text)
15607 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15608         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15609         int level = 0, move;
15610
15611         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15612         // first find outermost bracketing variation
15613         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15614             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15615                 if(*p == '{') wait = '}'; else
15616                 if(*p == '[') wait = ']'; else
15617                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15618                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15619             }
15620             if(*p == wait) wait = NULLCHAR; // closing ]} found
15621             p++;
15622         }
15623         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15624         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15625         end[1] = NULLCHAR; // clip off comment beyond variation
15626         ToNrEvent(currentMove-1);
15627         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15628         // kludge: use ParsePV() to append variation to game
15629         move = currentMove;
15630         ParsePV(start, TRUE);
15631         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15632         ClearPremoveHighlights();
15633         CommentPopDown();
15634         ToNrEvent(currentMove+1);
15635 }
15636