Allow Lion double-moves in opening book
[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, 2012, 2013, 2014 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     int flock(int f, int code);
59 #   define LOCK_EX 2
60 #   define SLASH '\\'
61
62 #   ifdef ARC_64BIT
63 #       define EGBB_NAME "egbbdll64.dll"
64 #   else
65 #       define EGBB_NAME "egbbdll.dll"
66 #   endif
67
68 #else
69
70 #   include <sys/file.h>
71 #   define SLASH '/'
72
73 #   include <dlfcn.h>
74 #   ifdef ARC_64BIT
75 #       define EGBB_NAME "egbbso64.so"
76 #   else
77 #       define EGBB_NAME "egbbso.so"
78 #   endif
79     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
80 #   define CDECL
81 #   define HMODULE void *
82 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 #   define GetProcAddress dlsym
84
85 #endif
86
87 #include "config.h"
88
89 #include <assert.h>
90 #include <stdio.h>
91 #include <ctype.h>
92 #include <errno.h>
93 #include <sys/types.h>
94 #include <sys/stat.h>
95 #include <math.h>
96 #include <ctype.h>
97
98 #if STDC_HEADERS
99 # include <stdlib.h>
100 # include <string.h>
101 # include <stdarg.h>
102 #else /* not STDC_HEADERS */
103 # if HAVE_STRING_H
104 #  include <string.h>
105 # else /* not HAVE_STRING_H */
106 #  include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
109
110 #if HAVE_SYS_FCNTL_H
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
113 # if HAVE_FCNTL_H
114 #  include <fcntl.h>
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
117
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
120 # include <time.h>
121 #else
122 # if HAVE_SYS_TIME_H
123 #  include <sys/time.h>
124 # else
125 #  include <time.h>
126 # endif
127 #endif
128
129 #if defined(_amigados) && !defined(__GNUC__)
130 struct timezone {
131     int tz_minuteswest;
132     int tz_dsttime;
133 };
134 extern int gettimeofday(struct timeval *, struct timezone *);
135 #endif
136
137 #if HAVE_UNISTD_H
138 # include <unistd.h>
139 #endif
140
141 #include "common.h"
142 #include "frontend.h"
143 #include "backend.h"
144 #include "parser.h"
145 #include "moves.h"
146 #if ZIPPY
147 # include "zippy.h"
148 #endif
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
152 #include "gettext.h"
153
154 #ifdef ENABLE_NLS
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
158 #else
159 # ifdef WIN32
160 #   define _(s) T_(s)
161 #   define N_(s) s
162 # else
163 #   define _(s) (s)
164 #   define N_(s) s
165 #   define T_(s) s
166 # endif
167 #endif
168
169
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172                          char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174                       char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187                    /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void StartChessProgram P((ChessProgramState *cps));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298 int border;       /* [HGM] width of board rim, needed to size seek graph  */
299
300 /* States for ics_getting_history */
301 #define H_FALSE 0
302 #define H_REQUESTED 1
303 #define H_GOT_REQ_HEADER 2
304 #define H_GOT_UNREQ_HEADER 3
305 #define H_GETTING_MOVES 4
306 #define H_GOT_UNWANTED_HEADER 5
307
308 /* whosays values for GameEnds */
309 #define GE_ICS 0
310 #define GE_ENGINE 1
311 #define GE_PLAYER 2
312 #define GE_FILE 3
313 #define GE_XBOARD 4
314 #define GE_ENGINE1 5
315 #define GE_ENGINE2 6
316
317 /* Maximum number of games in a cmail message */
318 #define CMAIL_MAX_GAMES 20
319
320 /* Different types of move when calling RegisterMove */
321 #define CMAIL_MOVE   0
322 #define CMAIL_RESIGN 1
323 #define CMAIL_DRAW   2
324 #define CMAIL_ACCEPT 3
325
326 /* Different types of result to remember for each game */
327 #define CMAIL_NOT_RESULT 0
328 #define CMAIL_OLD_RESULT 1
329 #define CMAIL_NEW_RESULT 2
330
331 /* Telnet protocol constants */
332 #define TN_WILL 0373
333 #define TN_WONT 0374
334 #define TN_DO   0375
335 #define TN_DONT 0376
336 #define TN_IAC  0377
337 #define TN_ECHO 0001
338 #define TN_SGA  0003
339 #define TN_PORT 23
340
341 char*
342 safeStrCpy (char *dst, const char *src, size_t count)
343 { // [HGM] made safe
344   int i;
345   assert( dst != NULL );
346   assert( src != NULL );
347   assert( count > 0 );
348
349   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
350   if(  i == count && dst[count-1] != NULLCHAR)
351     {
352       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
353       if(appData.debugMode)
354         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
355     }
356
357   return dst;
358 }
359
360 /* Some compiler can't cast u64 to double
361  * This function do the job for us:
362
363  * We use the highest bit for cast, this only
364  * works if the highest bit is not
365  * in use (This should not happen)
366  *
367  * We used this for all compiler
368  */
369 double
370 u64ToDouble (u64 value)
371 {
372   double r;
373   u64 tmp = value & u64Const(0x7fffffffffffffff);
374   r = (double)(s64)tmp;
375   if (value & u64Const(0x8000000000000000))
376        r +=  9.2233720368547758080e18; /* 2^63 */
377  return r;
378 }
379
380 /* Fake up flags for now, as we aren't keeping track of castling
381    availability yet. [HGM] Change of logic: the flag now only
382    indicates the type of castlings allowed by the rule of the game.
383    The actual rights themselves are maintained in the array
384    castlingRights, as part of the game history, and are not probed
385    by this function.
386  */
387 int
388 PosFlags (index)
389 {
390   int flags = F_ALL_CASTLE_OK;
391   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
392   switch (gameInfo.variant) {
393   case VariantSuicide:
394     flags &= ~F_ALL_CASTLE_OK;
395   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
396     flags |= F_IGNORE_CHECK;
397   case VariantLosers:
398     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
399     break;
400   case VariantAtomic:
401     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
402     break;
403   case VariantKriegspiel:
404     flags |= F_KRIEGSPIEL_CAPTURE;
405     break;
406   case VariantCapaRandom:
407   case VariantFischeRandom:
408     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
409   case VariantNoCastle:
410   case VariantShatranj:
411   case VariantCourier:
412   case VariantMakruk:
413   case VariantASEAN:
414   case VariantGrand:
415     flags &= ~F_ALL_CASTLE_OK;
416     break;
417   case VariantChu:
418   case VariantChuChess:
419   case VariantLion:
420     flags |= F_NULL_MOVE;
421     break;
422   default:
423     break;
424   }
425   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
426   return flags;
427 }
428
429 FILE *gameFileFP, *debugFP, *serverFP;
430 char *currentDebugFile; // [HGM] debug split: to remember name
431
432 /*
433     [AS] Note: sometimes, the sscanf() function is used to parse the input
434     into a fixed-size buffer. Because of this, we must be prepared to
435     receive strings as long as the size of the input buffer, which is currently
436     set to 4K for Windows and 8K for the rest.
437     So, we must either allocate sufficiently large buffers here, or
438     reduce the size of the input buffer in the input reading part.
439 */
440
441 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
442 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
443 char thinkOutput1[MSG_SIZ*10];
444
445 ChessProgramState first, second, pairing;
446
447 /* premove variables */
448 int premoveToX = 0;
449 int premoveToY = 0;
450 int premoveFromX = 0;
451 int premoveFromY = 0;
452 int premovePromoChar = 0;
453 int gotPremove = 0;
454 Boolean alarmSounded;
455 /* end premove variables */
456
457 char *ics_prefix = "$";
458 enum ICS_TYPE ics_type = ICS_GENERIC;
459
460 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
461 int pauseExamForwardMostMove = 0;
462 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
463 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
464 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
465 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
466 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
467 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
468 int whiteFlag = FALSE, blackFlag = FALSE;
469 int userOfferedDraw = FALSE;
470 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
471 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
472 int cmailMoveType[CMAIL_MAX_GAMES];
473 long ics_clock_paused = 0;
474 ProcRef icsPR = NoProc, cmailPR = NoProc;
475 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
476 GameMode gameMode = BeginningOfGame;
477 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
478 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
479 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
480 int hiddenThinkOutputState = 0; /* [AS] */
481 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
482 int adjudicateLossPlies = 6;
483 char white_holding[64], black_holding[64];
484 TimeMark lastNodeCountTime;
485 long lastNodeCount=0;
486 int shiftKey, controlKey; // [HGM] set by mouse handler
487
488 int have_sent_ICS_logon = 0;
489 int movesPerSession;
490 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
491 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
492 Boolean adjustedClock;
493 long timeControl_2; /* [AS] Allow separate time controls */
494 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
495 long timeRemaining[2][MAX_MOVES];
496 int matchGame = 0, nextGame = 0, roundNr = 0;
497 Boolean waitingForGame = FALSE, startingEngine = FALSE;
498 TimeMark programStartTime, pauseStart;
499 char ics_handle[MSG_SIZ];
500 int have_set_title = 0;
501
502 /* animateTraining preserves the state of appData.animate
503  * when Training mode is activated. This allows the
504  * response to be animated when appData.animate == TRUE and
505  * appData.animateDragging == TRUE.
506  */
507 Boolean animateTraining;
508
509 GameInfo gameInfo;
510
511 AppData appData;
512
513 Board boards[MAX_MOVES];
514 /* [HGM] Following 7 needed for accurate legality tests: */
515 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
516 signed char  initialRights[BOARD_FILES];
517 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
518 int   initialRulePlies, FENrulePlies;
519 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
520 int loadFlag = 0;
521 Boolean shuffleOpenings;
522 int mute; // mute all sounds
523
524 // [HGM] vari: next 12 to save and restore variations
525 #define MAX_VARIATIONS 10
526 int framePtr = MAX_MOVES-1; // points to free stack entry
527 int storedGames = 0;
528 int savedFirst[MAX_VARIATIONS];
529 int savedLast[MAX_VARIATIONS];
530 int savedFramePtr[MAX_VARIATIONS];
531 char *savedDetails[MAX_VARIATIONS];
532 ChessMove savedResult[MAX_VARIATIONS];
533
534 void PushTail P((int firstMove, int lastMove));
535 Boolean PopTail P((Boolean annotate));
536 void PushInner P((int firstMove, int lastMove));
537 void PopInner P((Boolean annotate));
538 void CleanupTail P((void));
539
540 ChessSquare  FIDEArray[2][BOARD_FILES] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
542         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
544         BlackKing, BlackBishop, BlackKnight, BlackRook }
545 };
546
547 ChessSquare twoKingsArray[2][BOARD_FILES] = {
548     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
549         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
551         BlackKing, BlackKing, BlackKnight, BlackRook }
552 };
553
554 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
555     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
556         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
557     { BlackRook, BlackMan, BlackBishop, BlackQueen,
558         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
559 };
560
561 ChessSquare SpartanArray[2][BOARD_FILES] = {
562     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
563         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
564     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
565         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
566 };
567
568 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
569     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
570         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
571     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
572         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
573 };
574
575 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
576     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
577         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
578     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
579         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
580 };
581
582 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
583     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
584         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
585     { BlackRook, BlackKnight, BlackMan, BlackFerz,
586         BlackKing, BlackMan, BlackKnight, BlackRook }
587 };
588
589 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
590     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
591         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
592     { BlackRook, BlackKnight, BlackMan, BlackFerz,
593         BlackKing, BlackMan, BlackKnight, BlackRook }
594 };
595
596 ChessSquare  lionArray[2][BOARD_FILES] = {
597     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
598         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
599     { BlackRook, BlackLion, BlackBishop, BlackQueen,
600         BlackKing, BlackBishop, BlackKnight, BlackRook }
601 };
602
603
604 #if (BOARD_FILES>=10)
605 ChessSquare ShogiArray[2][BOARD_FILES] = {
606     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
607         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
608     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
609         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
610 };
611
612 ChessSquare XiangqiArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
614         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
616         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
617 };
618
619 ChessSquare CapablancaArray[2][BOARD_FILES] = {
620     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
621         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
622     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
623         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
624 };
625
626 ChessSquare GreatArray[2][BOARD_FILES] = {
627     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
628         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
629     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
630         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
631 };
632
633 ChessSquare JanusArray[2][BOARD_FILES] = {
634     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
635         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
636     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
637         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
638 };
639
640 ChessSquare GrandArray[2][BOARD_FILES] = {
641     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
642         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
643     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
644         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
645 };
646
647 ChessSquare ChuChessArray[2][BOARD_FILES] = {
648     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
649         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
650     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
651         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
652 };
653
654 #ifdef GOTHIC
655 ChessSquare GothicArray[2][BOARD_FILES] = {
656     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
657         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
658     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
659         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
660 };
661 #else // !GOTHIC
662 #define GothicArray CapablancaArray
663 #endif // !GOTHIC
664
665 #ifdef FALCON
666 ChessSquare FalconArray[2][BOARD_FILES] = {
667     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
668         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
669     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
670         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
671 };
672 #else // !FALCON
673 #define FalconArray CapablancaArray
674 #endif // !FALCON
675
676 #else // !(BOARD_FILES>=10)
677 #define XiangqiPosition FIDEArray
678 #define CapablancaArray FIDEArray
679 #define GothicArray FIDEArray
680 #define GreatArray FIDEArray
681 #endif // !(BOARD_FILES>=10)
682
683 #if (BOARD_FILES>=12)
684 ChessSquare CourierArray[2][BOARD_FILES] = {
685     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
686         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
687     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
688         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
689 };
690 ChessSquare ChuArray[6][BOARD_FILES] = {
691     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
692       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
693     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
694       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
695     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
696       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
697     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
698       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
699     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
700       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
701     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
702       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
703 };
704 #else // !(BOARD_FILES>=12)
705 #define CourierArray CapablancaArray
706 #define ChuArray CapablancaArray
707 #endif // !(BOARD_FILES>=12)
708
709
710 Board initialPosition;
711
712
713 /* Convert str to a rating. Checks for special cases of "----",
714
715    "++++", etc. Also strips ()'s */
716 int
717 string_to_rating (char *str)
718 {
719   while(*str && !isdigit(*str)) ++str;
720   if (!*str)
721     return 0;   /* One of the special "no rating" cases */
722   else
723     return atoi(str);
724 }
725
726 void
727 ClearProgramStats ()
728 {
729     /* Init programStats */
730     programStats.movelist[0] = 0;
731     programStats.depth = 0;
732     programStats.nr_moves = 0;
733     programStats.moves_left = 0;
734     programStats.nodes = 0;
735     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
736     programStats.score = 0;
737     programStats.got_only_move = 0;
738     programStats.got_fail = 0;
739     programStats.line_is_book = 0;
740 }
741
742 void
743 CommonEngineInit ()
744 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
745     if (appData.firstPlaysBlack) {
746         first.twoMachinesColor = "black\n";
747         second.twoMachinesColor = "white\n";
748     } else {
749         first.twoMachinesColor = "white\n";
750         second.twoMachinesColor = "black\n";
751     }
752
753     first.other = &second;
754     second.other = &first;
755
756     { float norm = 1;
757         if(appData.timeOddsMode) {
758             norm = appData.timeOdds[0];
759             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
760         }
761         first.timeOdds  = appData.timeOdds[0]/norm;
762         second.timeOdds = appData.timeOdds[1]/norm;
763     }
764
765     if(programVersion) free(programVersion);
766     if (appData.noChessProgram) {
767         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
768         sprintf(programVersion, "%s", PACKAGE_STRING);
769     } else {
770       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
771       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
772       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
773     }
774 }
775
776 void
777 UnloadEngine (ChessProgramState *cps)
778 {
779         /* Kill off first chess program */
780         if (cps->isr != NULL)
781           RemoveInputSource(cps->isr);
782         cps->isr = NULL;
783
784         if (cps->pr != NoProc) {
785             ExitAnalyzeMode();
786             DoSleep( appData.delayBeforeQuit );
787             SendToProgram("quit\n", cps);
788             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
789         }
790         cps->pr = NoProc;
791         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
792 }
793
794 void
795 ClearOptions (ChessProgramState *cps)
796 {
797     int i;
798     cps->nrOptions = cps->comboCnt = 0;
799     for(i=0; i<MAX_OPTIONS; i++) {
800         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
801         cps->option[i].textValue = 0;
802     }
803 }
804
805 char *engineNames[] = {
806   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
807      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
808 N_("first"),
809   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
810      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
811 N_("second")
812 };
813
814 void
815 InitEngine (ChessProgramState *cps, int n)
816 {   // [HGM] all engine initialiation put in a function that does one engine
817
818     ClearOptions(cps);
819
820     cps->which = engineNames[n];
821     cps->maybeThinking = FALSE;
822     cps->pr = NoProc;
823     cps->isr = NULL;
824     cps->sendTime = 2;
825     cps->sendDrawOffers = 1;
826
827     cps->program = appData.chessProgram[n];
828     cps->host = appData.host[n];
829     cps->dir = appData.directory[n];
830     cps->initString = appData.engInitString[n];
831     cps->computerString = appData.computerString[n];
832     cps->useSigint  = TRUE;
833     cps->useSigterm = TRUE;
834     cps->reuse = appData.reuse[n];
835     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
836     cps->useSetboard = FALSE;
837     cps->useSAN = FALSE;
838     cps->usePing = FALSE;
839     cps->lastPing = 0;
840     cps->lastPong = 0;
841     cps->usePlayother = FALSE;
842     cps->useColors = TRUE;
843     cps->useUsermove = FALSE;
844     cps->sendICS = FALSE;
845     cps->sendName = appData.icsActive;
846     cps->sdKludge = FALSE;
847     cps->stKludge = FALSE;
848     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
849     TidyProgramName(cps->program, cps->host, cps->tidy);
850     cps->matchWins = 0;
851     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
852     cps->analysisSupport = 2; /* detect */
853     cps->analyzing = FALSE;
854     cps->initDone = FALSE;
855     cps->reload = FALSE;
856     cps->pseudo = appData.pseudo[n];
857
858     /* New features added by Tord: */
859     cps->useFEN960 = FALSE;
860     cps->useOOCastle = TRUE;
861     /* End of new features added by Tord. */
862     cps->fenOverride  = appData.fenOverride[n];
863
864     /* [HGM] time odds: set factor for each machine */
865     cps->timeOdds  = appData.timeOdds[n];
866
867     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
868     cps->accumulateTC = appData.accumulateTC[n];
869     cps->maxNrOfSessions = 1;
870
871     /* [HGM] debug */
872     cps->debug = FALSE;
873
874     cps->drawDepth = appData.drawDepth[n];
875     cps->supportsNPS = UNKNOWN;
876     cps->memSize = FALSE;
877     cps->maxCores = FALSE;
878     ASSIGN(cps->egtFormats, "");
879
880     /* [HGM] options */
881     cps->optionSettings  = appData.engOptions[n];
882
883     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
884     cps->isUCI = appData.isUCI[n]; /* [AS] */
885     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
886     cps->highlight = 0;
887
888     if (appData.protocolVersion[n] > PROTOVER
889         || appData.protocolVersion[n] < 1)
890       {
891         char buf[MSG_SIZ];
892         int len;
893
894         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
895                        appData.protocolVersion[n]);
896         if( (len >= MSG_SIZ) && appData.debugMode )
897           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
898
899         DisplayFatalError(buf, 0, 2);
900       }
901     else
902       {
903         cps->protocolVersion = appData.protocolVersion[n];
904       }
905
906     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
907     ParseFeatures(appData.featureDefaults, cps);
908 }
909
910 ChessProgramState *savCps;
911
912 GameMode oldMode;
913
914 void
915 LoadEngine ()
916 {
917     int i;
918     if(WaitForEngine(savCps, LoadEngine)) return;
919     CommonEngineInit(); // recalculate time odds
920     if(gameInfo.variant != StringToVariant(appData.variant)) {
921         // we changed variant when loading the engine; this forces us to reset
922         Reset(TRUE, savCps != &first);
923         oldMode = BeginningOfGame; // to prevent restoring old mode
924     }
925     InitChessProgram(savCps, FALSE);
926     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
927     DisplayMessage("", "");
928     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
929     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
930     ThawUI();
931     SetGNUMode();
932     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
933 }
934
935 void
936 ReplaceEngine (ChessProgramState *cps, int n)
937 {
938     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
939     keepInfo = 1;
940     if(oldMode != BeginningOfGame) EditGameEvent();
941     keepInfo = 0;
942     UnloadEngine(cps);
943     appData.noChessProgram = FALSE;
944     appData.clockMode = TRUE;
945     InitEngine(cps, n);
946     UpdateLogos(TRUE);
947     if(n) return; // only startup first engine immediately; second can wait
948     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
949     LoadEngine();
950 }
951
952 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
953 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
954
955 static char resetOptions[] =
956         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
957         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
958         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
959         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
960
961 void
962 FloatToFront(char **list, char *engineLine)
963 {
964     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
965     int i=0;
966     if(appData.recentEngines <= 0) return;
967     TidyProgramName(engineLine, "localhost", tidy+1);
968     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
969     strncpy(buf+1, *list, MSG_SIZ-50);
970     if(p = strstr(buf, tidy)) { // tidy name appears in list
971         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
972         while(*p++ = *++q); // squeeze out
973     }
974     strcat(tidy, buf+1); // put list behind tidy name
975     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
976     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
977     ASSIGN(*list, tidy+1);
978 }
979
980 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
981
982 void
983 Load (ChessProgramState *cps, int i)
984 {
985     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
986     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
987         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
988         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
989         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
990         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
991         appData.firstProtocolVersion = PROTOVER;
992         ParseArgsFromString(buf);
993         SwapEngines(i);
994         ReplaceEngine(cps, i);
995         FloatToFront(&appData.recentEngineList, engineLine);
996         return;
997     }
998     p = engineName;
999     while(q = strchr(p, SLASH)) p = q+1;
1000     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1001     if(engineDir[0] != NULLCHAR) {
1002         ASSIGN(appData.directory[i], engineDir); p = engineName;
1003     } else if(p != engineName) { // derive directory from engine path, when not given
1004         p[-1] = 0;
1005         ASSIGN(appData.directory[i], engineName);
1006         p[-1] = SLASH;
1007         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1008     } else { ASSIGN(appData.directory[i], "."); }
1009     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1010     if(params[0]) {
1011         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1012         snprintf(command, MSG_SIZ, "%s %s", p, params);
1013         p = command;
1014     }
1015     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1016     ASSIGN(appData.chessProgram[i], p);
1017     appData.isUCI[i] = isUCI;
1018     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1019     appData.hasOwnBookUCI[i] = hasBook;
1020     if(!nickName[0]) useNick = FALSE;
1021     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1022     if(addToList) {
1023         int len;
1024         char quote;
1025         q = firstChessProgramNames;
1026         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1027         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1028         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1029                         quote, p, quote, appData.directory[i],
1030                         useNick ? " -fn \"" : "",
1031                         useNick ? nickName : "",
1032                         useNick ? "\"" : "",
1033                         v1 ? " -firstProtocolVersion 1" : "",
1034                         hasBook ? "" : " -fNoOwnBookUCI",
1035                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1036                         storeVariant ? " -variant " : "",
1037                         storeVariant ? VariantName(gameInfo.variant) : "");
1038         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1039         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1040         if(insert != q) insert[-1] = NULLCHAR;
1041         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1042         if(q)   free(q);
1043         FloatToFront(&appData.recentEngineList, buf);
1044     }
1045     ReplaceEngine(cps, i);
1046 }
1047
1048 void
1049 InitTimeControls ()
1050 {
1051     int matched, min, sec;
1052     /*
1053      * Parse timeControl resource
1054      */
1055     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1056                           appData.movesPerSession)) {
1057         char buf[MSG_SIZ];
1058         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1059         DisplayFatalError(buf, 0, 2);
1060     }
1061
1062     /*
1063      * Parse searchTime resource
1064      */
1065     if (*appData.searchTime != NULLCHAR) {
1066         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1067         if (matched == 1) {
1068             searchTime = min * 60;
1069         } else if (matched == 2) {
1070             searchTime = min * 60 + sec;
1071         } else {
1072             char buf[MSG_SIZ];
1073             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1074             DisplayFatalError(buf, 0, 2);
1075         }
1076     }
1077 }
1078
1079 void
1080 InitBackEnd1 ()
1081 {
1082
1083     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1084     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1085
1086     GetTimeMark(&programStartTime);
1087     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1088     appData.seedBase = random() + (random()<<15);
1089     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1090
1091     ClearProgramStats();
1092     programStats.ok_to_send = 1;
1093     programStats.seen_stat = 0;
1094
1095     /*
1096      * Initialize game list
1097      */
1098     ListNew(&gameList);
1099
1100
1101     /*
1102      * Internet chess server status
1103      */
1104     if (appData.icsActive) {
1105         appData.matchMode = FALSE;
1106         appData.matchGames = 0;
1107 #if ZIPPY
1108         appData.noChessProgram = !appData.zippyPlay;
1109 #else
1110         appData.zippyPlay = FALSE;
1111         appData.zippyTalk = FALSE;
1112         appData.noChessProgram = TRUE;
1113 #endif
1114         if (*appData.icsHelper != NULLCHAR) {
1115             appData.useTelnet = TRUE;
1116             appData.telnetProgram = appData.icsHelper;
1117         }
1118     } else {
1119         appData.zippyTalk = appData.zippyPlay = FALSE;
1120     }
1121
1122     /* [AS] Initialize pv info list [HGM] and game state */
1123     {
1124         int i, j;
1125
1126         for( i=0; i<=framePtr; i++ ) {
1127             pvInfoList[i].depth = -1;
1128             boards[i][EP_STATUS] = EP_NONE;
1129             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1130         }
1131     }
1132
1133     InitTimeControls();
1134
1135     /* [AS] Adjudication threshold */
1136     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1137
1138     InitEngine(&first, 0);
1139     InitEngine(&second, 1);
1140     CommonEngineInit();
1141
1142     pairing.which = "pairing"; // pairing engine
1143     pairing.pr = NoProc;
1144     pairing.isr = NULL;
1145     pairing.program = appData.pairingEngine;
1146     pairing.host = "localhost";
1147     pairing.dir = ".";
1148
1149     if (appData.icsActive) {
1150         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1151     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1152         appData.clockMode = FALSE;
1153         first.sendTime = second.sendTime = 0;
1154     }
1155
1156 #if ZIPPY
1157     /* Override some settings from environment variables, for backward
1158        compatibility.  Unfortunately it's not feasible to have the env
1159        vars just set defaults, at least in xboard.  Ugh.
1160     */
1161     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1162       ZippyInit();
1163     }
1164 #endif
1165
1166     if (!appData.icsActive) {
1167       char buf[MSG_SIZ];
1168       int len;
1169
1170       /* Check for variants that are supported only in ICS mode,
1171          or not at all.  Some that are accepted here nevertheless
1172          have bugs; see comments below.
1173       */
1174       VariantClass variant = StringToVariant(appData.variant);
1175       switch (variant) {
1176       case VariantBughouse:     /* need four players and two boards */
1177       case VariantKriegspiel:   /* need to hide pieces and move details */
1178         /* case VariantFischeRandom: (Fabien: moved below) */
1179         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1180         if( (len >= MSG_SIZ) && appData.debugMode )
1181           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1182
1183         DisplayFatalError(buf, 0, 2);
1184         return;
1185
1186       case VariantUnknown:
1187       case VariantLoadable:
1188       case Variant29:
1189       case Variant30:
1190       case Variant31:
1191       case Variant32:
1192       case Variant33:
1193       case Variant34:
1194       case Variant35:
1195       case Variant36:
1196       default:
1197         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1198         if( (len >= MSG_SIZ) && appData.debugMode )
1199           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1200
1201         DisplayFatalError(buf, 0, 2);
1202         return;
1203
1204       case VariantNormal:     /* definitely works! */
1205         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1206           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1207           return;
1208         }
1209       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1210       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1211       case VariantGothic:     /* [HGM] should work */
1212       case VariantCapablanca: /* [HGM] should work */
1213       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1214       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1215       case VariantChu:        /* [HGM] experimental */
1216       case VariantKnightmate: /* [HGM] should work */
1217       case VariantCylinder:   /* [HGM] untested */
1218       case VariantFalcon:     /* [HGM] untested */
1219       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1220                                  offboard interposition not understood */
1221       case VariantWildCastle: /* pieces not automatically shuffled */
1222       case VariantNoCastle:   /* pieces not automatically shuffled */
1223       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1224       case VariantLosers:     /* should work except for win condition,
1225                                  and doesn't know captures are mandatory */
1226       case VariantSuicide:    /* should work except for win condition,
1227                                  and doesn't know captures are mandatory */
1228       case VariantGiveaway:   /* should work except for win condition,
1229                                  and doesn't know captures are mandatory */
1230       case VariantTwoKings:   /* should work */
1231       case VariantAtomic:     /* should work except for win condition */
1232       case Variant3Check:     /* should work except for win condition */
1233       case VariantShatranj:   /* should work except for all win conditions */
1234       case VariantMakruk:     /* should work except for draw countdown */
1235       case VariantASEAN :     /* should work except for draw countdown */
1236       case VariantBerolina:   /* might work if TestLegality is off */
1237       case VariantCapaRandom: /* should work */
1238       case VariantJanus:      /* should work */
1239       case VariantSuper:      /* experimental */
1240       case VariantGreat:      /* experimental, requires legality testing to be off */
1241       case VariantSChess:     /* S-Chess, should work */
1242       case VariantGrand:      /* should work */
1243       case VariantSpartan:    /* should work */
1244       case VariantLion:       /* should work */
1245       case VariantChuChess:   /* should work */
1246         break;
1247       }
1248     }
1249
1250 }
1251
1252 int
1253 NextIntegerFromString (char ** str, long * value)
1254 {
1255     int result = -1;
1256     char * s = *str;
1257
1258     while( *s == ' ' || *s == '\t' ) {
1259         s++;
1260     }
1261
1262     *value = 0;
1263
1264     if( *s >= '0' && *s <= '9' ) {
1265         while( *s >= '0' && *s <= '9' ) {
1266             *value = *value * 10 + (*s - '0');
1267             s++;
1268         }
1269
1270         result = 0;
1271     }
1272
1273     *str = s;
1274
1275     return result;
1276 }
1277
1278 int
1279 NextTimeControlFromString (char ** str, long * value)
1280 {
1281     long temp;
1282     int result = NextIntegerFromString( str, &temp );
1283
1284     if( result == 0 ) {
1285         *value = temp * 60; /* Minutes */
1286         if( **str == ':' ) {
1287             (*str)++;
1288             result = NextIntegerFromString( str, &temp );
1289             *value += temp; /* Seconds */
1290         }
1291     }
1292
1293     return result;
1294 }
1295
1296 int
1297 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1298 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1299     int result = -1, type = 0; long temp, temp2;
1300
1301     if(**str != ':') return -1; // old params remain in force!
1302     (*str)++;
1303     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1304     if( NextIntegerFromString( str, &temp ) ) return -1;
1305     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1306
1307     if(**str != '/') {
1308         /* time only: incremental or sudden-death time control */
1309         if(**str == '+') { /* increment follows; read it */
1310             (*str)++;
1311             if(**str == '!') type = *(*str)++; // Bronstein TC
1312             if(result = NextIntegerFromString( str, &temp2)) return -1;
1313             *inc = temp2 * 1000;
1314             if(**str == '.') { // read fraction of increment
1315                 char *start = ++(*str);
1316                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1317                 temp2 *= 1000;
1318                 while(start++ < *str) temp2 /= 10;
1319                 *inc += temp2;
1320             }
1321         } else *inc = 0;
1322         *moves = 0; *tc = temp * 1000; *incType = type;
1323         return 0;
1324     }
1325
1326     (*str)++; /* classical time control */
1327     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1328
1329     if(result == 0) {
1330         *moves = temp;
1331         *tc    = temp2 * 1000;
1332         *inc   = 0;
1333         *incType = type;
1334     }
1335     return result;
1336 }
1337
1338 int
1339 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1340 {   /* [HGM] get time to add from the multi-session time-control string */
1341     int incType, moves=1; /* kludge to force reading of first session */
1342     long time, increment;
1343     char *s = tcString;
1344
1345     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1346     do {
1347         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1348         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1349         if(movenr == -1) return time;    /* last move before new session     */
1350         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1351         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1352         if(!moves) return increment;     /* current session is incremental   */
1353         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1354     } while(movenr >= -1);               /* try again for next session       */
1355
1356     return 0; // no new time quota on this move
1357 }
1358
1359 int
1360 ParseTimeControl (char *tc, float ti, int mps)
1361 {
1362   long tc1;
1363   long tc2;
1364   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1365   int min, sec=0;
1366
1367   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1368   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1369       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1370   if(ti > 0) {
1371
1372     if(mps)
1373       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1374     else
1375       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1376   } else {
1377     if(mps)
1378       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1379     else
1380       snprintf(buf, MSG_SIZ, ":%s", mytc);
1381   }
1382   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1383
1384   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1385     return FALSE;
1386   }
1387
1388   if( *tc == '/' ) {
1389     /* Parse second time control */
1390     tc++;
1391
1392     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1393       return FALSE;
1394     }
1395
1396     if( tc2 == 0 ) {
1397       return FALSE;
1398     }
1399
1400     timeControl_2 = tc2 * 1000;
1401   }
1402   else {
1403     timeControl_2 = 0;
1404   }
1405
1406   if( tc1 == 0 ) {
1407     return FALSE;
1408   }
1409
1410   timeControl = tc1 * 1000;
1411
1412   if (ti >= 0) {
1413     timeIncrement = ti * 1000;  /* convert to ms */
1414     movesPerSession = 0;
1415   } else {
1416     timeIncrement = 0;
1417     movesPerSession = mps;
1418   }
1419   return TRUE;
1420 }
1421
1422 void
1423 InitBackEnd2 ()
1424 {
1425     if (appData.debugMode) {
1426 #    ifdef __GIT_VERSION
1427       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1428 #    else
1429       fprintf(debugFP, "Version: %s\n", programVersion);
1430 #    endif
1431     }
1432     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1433
1434     set_cont_sequence(appData.wrapContSeq);
1435     if (appData.matchGames > 0) {
1436         appData.matchMode = TRUE;
1437     } else if (appData.matchMode) {
1438         appData.matchGames = 1;
1439     }
1440     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1441         appData.matchGames = appData.sameColorGames;
1442     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1443         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1444         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1445     }
1446     Reset(TRUE, FALSE);
1447     if (appData.noChessProgram || first.protocolVersion == 1) {
1448       InitBackEnd3();
1449     } else {
1450       /* kludge: allow timeout for initial "feature" commands */
1451       FreezeUI();
1452       DisplayMessage("", _("Starting chess program"));
1453       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1454     }
1455 }
1456
1457 int
1458 CalculateIndex (int index, int gameNr)
1459 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1460     int res;
1461     if(index > 0) return index; // fixed nmber
1462     if(index == 0) return 1;
1463     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1464     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1465     return res;
1466 }
1467
1468 int
1469 LoadGameOrPosition (int gameNr)
1470 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1471     if (*appData.loadGameFile != NULLCHAR) {
1472         if (!LoadGameFromFile(appData.loadGameFile,
1473                 CalculateIndex(appData.loadGameIndex, gameNr),
1474                               appData.loadGameFile, FALSE)) {
1475             DisplayFatalError(_("Bad game file"), 0, 1);
1476             return 0;
1477         }
1478     } else if (*appData.loadPositionFile != NULLCHAR) {
1479         if (!LoadPositionFromFile(appData.loadPositionFile,
1480                 CalculateIndex(appData.loadPositionIndex, gameNr),
1481                                   appData.loadPositionFile)) {
1482             DisplayFatalError(_("Bad position file"), 0, 1);
1483             return 0;
1484         }
1485     }
1486     return 1;
1487 }
1488
1489 void
1490 ReserveGame (int gameNr, char resChar)
1491 {
1492     FILE *tf = fopen(appData.tourneyFile, "r+");
1493     char *p, *q, c, buf[MSG_SIZ];
1494     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1495     safeStrCpy(buf, lastMsg, MSG_SIZ);
1496     DisplayMessage(_("Pick new game"), "");
1497     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1498     ParseArgsFromFile(tf);
1499     p = q = appData.results;
1500     if(appData.debugMode) {
1501       char *r = appData.participants;
1502       fprintf(debugFP, "results = '%s'\n", p);
1503       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1504       fprintf(debugFP, "\n");
1505     }
1506     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1507     nextGame = q - p;
1508     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1509     safeStrCpy(q, p, strlen(p) + 2);
1510     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1511     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1512     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1513         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1514         q[nextGame] = '*';
1515     }
1516     fseek(tf, -(strlen(p)+4), SEEK_END);
1517     c = fgetc(tf);
1518     if(c != '"') // depending on DOS or Unix line endings we can be one off
1519          fseek(tf, -(strlen(p)+2), SEEK_END);
1520     else fseek(tf, -(strlen(p)+3), SEEK_END);
1521     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1522     DisplayMessage(buf, "");
1523     free(p); appData.results = q;
1524     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1525        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1526       int round = appData.defaultMatchGames * appData.tourneyType;
1527       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1528          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1529         UnloadEngine(&first);  // next game belongs to other pairing;
1530         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1531     }
1532     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1533 }
1534
1535 void
1536 MatchEvent (int mode)
1537 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1538         int dummy;
1539         if(matchMode) { // already in match mode: switch it off
1540             abortMatch = TRUE;
1541             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1542             return;
1543         }
1544 //      if(gameMode != BeginningOfGame) {
1545 //          DisplayError(_("You can only start a match from the initial position."), 0);
1546 //          return;
1547 //      }
1548         abortMatch = FALSE;
1549         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1550         /* Set up machine vs. machine match */
1551         nextGame = 0;
1552         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1553         if(appData.tourneyFile[0]) {
1554             ReserveGame(-1, 0);
1555             if(nextGame > appData.matchGames) {
1556                 char buf[MSG_SIZ];
1557                 if(strchr(appData.results, '*') == NULL) {
1558                     FILE *f;
1559                     appData.tourneyCycles++;
1560                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1561                         fclose(f);
1562                         NextTourneyGame(-1, &dummy);
1563                         ReserveGame(-1, 0);
1564                         if(nextGame <= appData.matchGames) {
1565                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1566                             matchMode = mode;
1567                             ScheduleDelayedEvent(NextMatchGame, 10000);
1568                             return;
1569                         }
1570                     }
1571                 }
1572                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1573                 DisplayError(buf, 0);
1574                 appData.tourneyFile[0] = 0;
1575                 return;
1576             }
1577         } else
1578         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1579             DisplayFatalError(_("Can't have a match with no chess programs"),
1580                               0, 2);
1581             return;
1582         }
1583         matchMode = mode;
1584         matchGame = roundNr = 1;
1585         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1586         NextMatchGame();
1587 }
1588
1589 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1590
1591 void
1592 InitBackEnd3 P((void))
1593 {
1594     GameMode initialMode;
1595     char buf[MSG_SIZ];
1596     int err, len;
1597
1598     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1599        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1600         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1601        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1602        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1603         char c, *q = first.variants, *p = strchr(q, ',');
1604         if(p) *p = NULLCHAR;
1605         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1606             int w, h, s;
1607             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1608                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1609             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1610             Reset(TRUE, FALSE);         // and re-initialize
1611         }
1612         if(p) *p = ',';
1613     }
1614
1615     InitChessProgram(&first, startedFromSetupPosition);
1616
1617     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1618         free(programVersion);
1619         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1620         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1621         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1622     }
1623
1624     if (appData.icsActive) {
1625 #ifdef WIN32
1626         /* [DM] Make a console window if needed [HGM] merged ifs */
1627         ConsoleCreate();
1628 #endif
1629         err = establish();
1630         if (err != 0)
1631           {
1632             if (*appData.icsCommPort != NULLCHAR)
1633               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1634                              appData.icsCommPort);
1635             else
1636               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1637                         appData.icsHost, appData.icsPort);
1638
1639             if( (len >= MSG_SIZ) && appData.debugMode )
1640               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1641
1642             DisplayFatalError(buf, err, 1);
1643             return;
1644         }
1645         SetICSMode();
1646         telnetISR =
1647           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1648         fromUserISR =
1649           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1650         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1651             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1652     } else if (appData.noChessProgram) {
1653         SetNCPMode();
1654     } else {
1655         SetGNUMode();
1656     }
1657
1658     if (*appData.cmailGameName != NULLCHAR) {
1659         SetCmailMode();
1660         OpenLoopback(&cmailPR);
1661         cmailISR =
1662           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1663     }
1664
1665     ThawUI();
1666     DisplayMessage("", "");
1667     if (StrCaseCmp(appData.initialMode, "") == 0) {
1668       initialMode = BeginningOfGame;
1669       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1670         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1671         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1672         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1673         ModeHighlight();
1674       }
1675     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1676       initialMode = TwoMachinesPlay;
1677     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1678       initialMode = AnalyzeFile;
1679     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1680       initialMode = AnalyzeMode;
1681     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1682       initialMode = MachinePlaysWhite;
1683     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1684       initialMode = MachinePlaysBlack;
1685     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1686       initialMode = EditGame;
1687     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1688       initialMode = EditPosition;
1689     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1690       initialMode = Training;
1691     } else {
1692       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1693       if( (len >= MSG_SIZ) && appData.debugMode )
1694         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1695
1696       DisplayFatalError(buf, 0, 2);
1697       return;
1698     }
1699
1700     if (appData.matchMode) {
1701         if(appData.tourneyFile[0]) { // start tourney from command line
1702             FILE *f;
1703             if(f = fopen(appData.tourneyFile, "r")) {
1704                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1705                 fclose(f);
1706                 appData.clockMode = TRUE;
1707                 SetGNUMode();
1708             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1709         }
1710         MatchEvent(TRUE);
1711     } else if (*appData.cmailGameName != NULLCHAR) {
1712         /* Set up cmail mode */
1713         ReloadCmailMsgEvent(TRUE);
1714     } else {
1715         /* Set up other modes */
1716         if (initialMode == AnalyzeFile) {
1717           if (*appData.loadGameFile == NULLCHAR) {
1718             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1719             return;
1720           }
1721         }
1722         if (*appData.loadGameFile != NULLCHAR) {
1723             (void) LoadGameFromFile(appData.loadGameFile,
1724                                     appData.loadGameIndex,
1725                                     appData.loadGameFile, TRUE);
1726         } else if (*appData.loadPositionFile != NULLCHAR) {
1727             (void) LoadPositionFromFile(appData.loadPositionFile,
1728                                         appData.loadPositionIndex,
1729                                         appData.loadPositionFile);
1730             /* [HGM] try to make self-starting even after FEN load */
1731             /* to allow automatic setup of fairy variants with wtm */
1732             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1733                 gameMode = BeginningOfGame;
1734                 setboardSpoiledMachineBlack = 1;
1735             }
1736             /* [HGM] loadPos: make that every new game uses the setup */
1737             /* from file as long as we do not switch variant          */
1738             if(!blackPlaysFirst) {
1739                 startedFromPositionFile = TRUE;
1740                 CopyBoard(filePosition, boards[0]);
1741             }
1742         }
1743         if (initialMode == AnalyzeMode) {
1744           if (appData.noChessProgram) {
1745             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1746             return;
1747           }
1748           if (appData.icsActive) {
1749             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1750             return;
1751           }
1752           AnalyzeModeEvent();
1753         } else if (initialMode == AnalyzeFile) {
1754           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1755           ShowThinkingEvent();
1756           AnalyzeFileEvent();
1757           AnalysisPeriodicEvent(1);
1758         } else if (initialMode == MachinePlaysWhite) {
1759           if (appData.noChessProgram) {
1760             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1761                               0, 2);
1762             return;
1763           }
1764           if (appData.icsActive) {
1765             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1766                               0, 2);
1767             return;
1768           }
1769           MachineWhiteEvent();
1770         } else if (initialMode == MachinePlaysBlack) {
1771           if (appData.noChessProgram) {
1772             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1773                               0, 2);
1774             return;
1775           }
1776           if (appData.icsActive) {
1777             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1778                               0, 2);
1779             return;
1780           }
1781           MachineBlackEvent();
1782         } else if (initialMode == TwoMachinesPlay) {
1783           if (appData.noChessProgram) {
1784             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1785                               0, 2);
1786             return;
1787           }
1788           if (appData.icsActive) {
1789             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1790                               0, 2);
1791             return;
1792           }
1793           TwoMachinesEvent();
1794         } else if (initialMode == EditGame) {
1795           EditGameEvent();
1796         } else if (initialMode == EditPosition) {
1797           EditPositionEvent();
1798         } else if (initialMode == Training) {
1799           if (*appData.loadGameFile == NULLCHAR) {
1800             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1801             return;
1802           }
1803           TrainingEvent();
1804         }
1805     }
1806 }
1807
1808 void
1809 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1810 {
1811     DisplayBook(current+1);
1812
1813     MoveHistorySet( movelist, first, last, current, pvInfoList );
1814
1815     EvalGraphSet( first, last, current, pvInfoList );
1816
1817     MakeEngineOutputTitle();
1818 }
1819
1820 /*
1821  * Establish will establish a contact to a remote host.port.
1822  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1823  *  used to talk to the host.
1824  * Returns 0 if okay, error code if not.
1825  */
1826 int
1827 establish ()
1828 {
1829     char buf[MSG_SIZ];
1830
1831     if (*appData.icsCommPort != NULLCHAR) {
1832         /* Talk to the host through a serial comm port */
1833         return OpenCommPort(appData.icsCommPort, &icsPR);
1834
1835     } else if (*appData.gateway != NULLCHAR) {
1836         if (*appData.remoteShell == NULLCHAR) {
1837             /* Use the rcmd protocol to run telnet program on a gateway host */
1838             snprintf(buf, sizeof(buf), "%s %s %s",
1839                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1840             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1841
1842         } else {
1843             /* Use the rsh program to run telnet program on a gateway host */
1844             if (*appData.remoteUser == NULLCHAR) {
1845                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1846                         appData.gateway, appData.telnetProgram,
1847                         appData.icsHost, appData.icsPort);
1848             } else {
1849                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1850                         appData.remoteShell, appData.gateway,
1851                         appData.remoteUser, appData.telnetProgram,
1852                         appData.icsHost, appData.icsPort);
1853             }
1854             return StartChildProcess(buf, "", &icsPR);
1855
1856         }
1857     } else if (appData.useTelnet) {
1858         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1859
1860     } else {
1861         /* TCP socket interface differs somewhat between
1862            Unix and NT; handle details in the front end.
1863            */
1864         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1865     }
1866 }
1867
1868 void
1869 EscapeExpand (char *p, char *q)
1870 {       // [HGM] initstring: routine to shape up string arguments
1871         while(*p++ = *q++) if(p[-1] == '\\')
1872             switch(*q++) {
1873                 case 'n': p[-1] = '\n'; break;
1874                 case 'r': p[-1] = '\r'; break;
1875                 case 't': p[-1] = '\t'; break;
1876                 case '\\': p[-1] = '\\'; break;
1877                 case 0: *p = 0; return;
1878                 default: p[-1] = q[-1]; break;
1879             }
1880 }
1881
1882 void
1883 show_bytes (FILE *fp, char *buf, int count)
1884 {
1885     while (count--) {
1886         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1887             fprintf(fp, "\\%03o", *buf & 0xff);
1888         } else {
1889             putc(*buf, fp);
1890         }
1891         buf++;
1892     }
1893     fflush(fp);
1894 }
1895
1896 /* Returns an errno value */
1897 int
1898 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1899 {
1900     char buf[8192], *p, *q, *buflim;
1901     int left, newcount, outcount;
1902
1903     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1904         *appData.gateway != NULLCHAR) {
1905         if (appData.debugMode) {
1906             fprintf(debugFP, ">ICS: ");
1907             show_bytes(debugFP, message, count);
1908             fprintf(debugFP, "\n");
1909         }
1910         return OutputToProcess(pr, message, count, outError);
1911     }
1912
1913     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1914     p = message;
1915     q = buf;
1916     left = count;
1917     newcount = 0;
1918     while (left) {
1919         if (q >= buflim) {
1920             if (appData.debugMode) {
1921                 fprintf(debugFP, ">ICS: ");
1922                 show_bytes(debugFP, buf, newcount);
1923                 fprintf(debugFP, "\n");
1924             }
1925             outcount = OutputToProcess(pr, buf, newcount, outError);
1926             if (outcount < newcount) return -1; /* to be sure */
1927             q = buf;
1928             newcount = 0;
1929         }
1930         if (*p == '\n') {
1931             *q++ = '\r';
1932             newcount++;
1933         } else if (((unsigned char) *p) == TN_IAC) {
1934             *q++ = (char) TN_IAC;
1935             newcount ++;
1936         }
1937         *q++ = *p++;
1938         newcount++;
1939         left--;
1940     }
1941     if (appData.debugMode) {
1942         fprintf(debugFP, ">ICS: ");
1943         show_bytes(debugFP, buf, newcount);
1944         fprintf(debugFP, "\n");
1945     }
1946     outcount = OutputToProcess(pr, buf, newcount, outError);
1947     if (outcount < newcount) return -1; /* to be sure */
1948     return count;
1949 }
1950
1951 void
1952 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1953 {
1954     int outError, outCount;
1955     static int gotEof = 0;
1956     static FILE *ini;
1957
1958     /* Pass data read from player on to ICS */
1959     if (count > 0) {
1960         gotEof = 0;
1961         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1962         if (outCount < count) {
1963             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1964         }
1965         if(have_sent_ICS_logon == 2) {
1966           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1967             fprintf(ini, "%s", message);
1968             have_sent_ICS_logon = 3;
1969           } else
1970             have_sent_ICS_logon = 1;
1971         } else if(have_sent_ICS_logon == 3) {
1972             fprintf(ini, "%s", message);
1973             fclose(ini);
1974           have_sent_ICS_logon = 1;
1975         }
1976     } else if (count < 0) {
1977         RemoveInputSource(isr);
1978         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1979     } else if (gotEof++ > 0) {
1980         RemoveInputSource(isr);
1981         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1982     }
1983 }
1984
1985 void
1986 KeepAlive ()
1987 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1988     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1989     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1990     SendToICS("date\n");
1991     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1992 }
1993
1994 /* added routine for printf style output to ics */
1995 void
1996 ics_printf (char *format, ...)
1997 {
1998     char buffer[MSG_SIZ];
1999     va_list args;
2000
2001     va_start(args, format);
2002     vsnprintf(buffer, sizeof(buffer), format, args);
2003     buffer[sizeof(buffer)-1] = '\0';
2004     SendToICS(buffer);
2005     va_end(args);
2006 }
2007
2008 void
2009 SendToICS (char *s)
2010 {
2011     int count, outCount, outError;
2012
2013     if (icsPR == NoProc) return;
2014
2015     count = strlen(s);
2016     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2017     if (outCount < count) {
2018         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2019     }
2020 }
2021
2022 /* This is used for sending logon scripts to the ICS. Sending
2023    without a delay causes problems when using timestamp on ICC
2024    (at least on my machine). */
2025 void
2026 SendToICSDelayed (char *s, long msdelay)
2027 {
2028     int count, outCount, outError;
2029
2030     if (icsPR == NoProc) return;
2031
2032     count = strlen(s);
2033     if (appData.debugMode) {
2034         fprintf(debugFP, ">ICS: ");
2035         show_bytes(debugFP, s, count);
2036         fprintf(debugFP, "\n");
2037     }
2038     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2039                                       msdelay);
2040     if (outCount < count) {
2041         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2042     }
2043 }
2044
2045
2046 /* Remove all highlighting escape sequences in s
2047    Also deletes any suffix starting with '('
2048    */
2049 char *
2050 StripHighlightAndTitle (char *s)
2051 {
2052     static char retbuf[MSG_SIZ];
2053     char *p = retbuf;
2054
2055     while (*s != NULLCHAR) {
2056         while (*s == '\033') {
2057             while (*s != NULLCHAR && !isalpha(*s)) s++;
2058             if (*s != NULLCHAR) s++;
2059         }
2060         while (*s != NULLCHAR && *s != '\033') {
2061             if (*s == '(' || *s == '[') {
2062                 *p = NULLCHAR;
2063                 return retbuf;
2064             }
2065             *p++ = *s++;
2066         }
2067     }
2068     *p = NULLCHAR;
2069     return retbuf;
2070 }
2071
2072 /* Remove all highlighting escape sequences in s */
2073 char *
2074 StripHighlight (char *s)
2075 {
2076     static char retbuf[MSG_SIZ];
2077     char *p = retbuf;
2078
2079     while (*s != NULLCHAR) {
2080         while (*s == '\033') {
2081             while (*s != NULLCHAR && !isalpha(*s)) s++;
2082             if (*s != NULLCHAR) s++;
2083         }
2084         while (*s != NULLCHAR && *s != '\033') {
2085             *p++ = *s++;
2086         }
2087     }
2088     *p = NULLCHAR;
2089     return retbuf;
2090 }
2091
2092 char engineVariant[MSG_SIZ];
2093 char *variantNames[] = VARIANT_NAMES;
2094 char *
2095 VariantName (VariantClass v)
2096 {
2097     if(v == VariantUnknown || *engineVariant) return engineVariant;
2098     return variantNames[v];
2099 }
2100
2101
2102 /* Identify a variant from the strings the chess servers use or the
2103    PGN Variant tag names we use. */
2104 VariantClass
2105 StringToVariant (char *e)
2106 {
2107     char *p;
2108     int wnum = -1;
2109     VariantClass v = VariantNormal;
2110     int i, found = FALSE;
2111     char buf[MSG_SIZ], c;
2112     int len;
2113
2114     if (!e) return v;
2115
2116     /* [HGM] skip over optional board-size prefixes */
2117     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2118         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2119         while( *e++ != '_');
2120     }
2121
2122     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2123         v = VariantNormal;
2124         found = TRUE;
2125     } else
2126     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2127       if (p = StrCaseStr(e, variantNames[i])) {
2128         if(p && i >= VariantShogi && (p != e || isalpha(p[strlen(variantNames[i])]))) continue;
2129         v = (VariantClass) i;
2130         found = TRUE;
2131         break;
2132       }
2133     }
2134
2135     if (!found) {
2136       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2137           || StrCaseStr(e, "wild/fr")
2138           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2139         v = VariantFischeRandom;
2140       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2141                  (i = 1, p = StrCaseStr(e, "w"))) {
2142         p += i;
2143         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2144         if (isdigit(*p)) {
2145           wnum = atoi(p);
2146         } else {
2147           wnum = -1;
2148         }
2149         switch (wnum) {
2150         case 0: /* FICS only, actually */
2151         case 1:
2152           /* Castling legal even if K starts on d-file */
2153           v = VariantWildCastle;
2154           break;
2155         case 2:
2156         case 3:
2157         case 4:
2158           /* Castling illegal even if K & R happen to start in
2159              normal positions. */
2160           v = VariantNoCastle;
2161           break;
2162         case 5:
2163         case 7:
2164         case 8:
2165         case 10:
2166         case 11:
2167         case 12:
2168         case 13:
2169         case 14:
2170         case 15:
2171         case 18:
2172         case 19:
2173           /* Castling legal iff K & R start in normal positions */
2174           v = VariantNormal;
2175           break;
2176         case 6:
2177         case 20:
2178         case 21:
2179           /* Special wilds for position setup; unclear what to do here */
2180           v = VariantLoadable;
2181           break;
2182         case 9:
2183           /* Bizarre ICC game */
2184           v = VariantTwoKings;
2185           break;
2186         case 16:
2187           v = VariantKriegspiel;
2188           break;
2189         case 17:
2190           v = VariantLosers;
2191           break;
2192         case 22:
2193           v = VariantFischeRandom;
2194           break;
2195         case 23:
2196           v = VariantCrazyhouse;
2197           break;
2198         case 24:
2199           v = VariantBughouse;
2200           break;
2201         case 25:
2202           v = Variant3Check;
2203           break;
2204         case 26:
2205           /* Not quite the same as FICS suicide! */
2206           v = VariantGiveaway;
2207           break;
2208         case 27:
2209           v = VariantAtomic;
2210           break;
2211         case 28:
2212           v = VariantShatranj;
2213           break;
2214
2215         /* Temporary names for future ICC types.  The name *will* change in
2216            the next xboard/WinBoard release after ICC defines it. */
2217         case 29:
2218           v = Variant29;
2219           break;
2220         case 30:
2221           v = Variant30;
2222           break;
2223         case 31:
2224           v = Variant31;
2225           break;
2226         case 32:
2227           v = Variant32;
2228           break;
2229         case 33:
2230           v = Variant33;
2231           break;
2232         case 34:
2233           v = Variant34;
2234           break;
2235         case 35:
2236           v = Variant35;
2237           break;
2238         case 36:
2239           v = Variant36;
2240           break;
2241         case 37:
2242           v = VariantShogi;
2243           break;
2244         case 38:
2245           v = VariantXiangqi;
2246           break;
2247         case 39:
2248           v = VariantCourier;
2249           break;
2250         case 40:
2251           v = VariantGothic;
2252           break;
2253         case 41:
2254           v = VariantCapablanca;
2255           break;
2256         case 42:
2257           v = VariantKnightmate;
2258           break;
2259         case 43:
2260           v = VariantFairy;
2261           break;
2262         case 44:
2263           v = VariantCylinder;
2264           break;
2265         case 45:
2266           v = VariantFalcon;
2267           break;
2268         case 46:
2269           v = VariantCapaRandom;
2270           break;
2271         case 47:
2272           v = VariantBerolina;
2273           break;
2274         case 48:
2275           v = VariantJanus;
2276           break;
2277         case 49:
2278           v = VariantSuper;
2279           break;
2280         case 50:
2281           v = VariantGreat;
2282           break;
2283         case -1:
2284           /* Found "wild" or "w" in the string but no number;
2285              must assume it's normal chess. */
2286           v = VariantNormal;
2287           break;
2288         default:
2289           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2290           if( (len >= MSG_SIZ) && appData.debugMode )
2291             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2292
2293           DisplayError(buf, 0);
2294           v = VariantUnknown;
2295           break;
2296         }
2297       }
2298     }
2299     if (appData.debugMode) {
2300       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2301               e, wnum, VariantName(v));
2302     }
2303     return v;
2304 }
2305
2306 static int leftover_start = 0, leftover_len = 0;
2307 char star_match[STAR_MATCH_N][MSG_SIZ];
2308
2309 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2310    advance *index beyond it, and set leftover_start to the new value of
2311    *index; else return FALSE.  If pattern contains the character '*', it
2312    matches any sequence of characters not containing '\r', '\n', or the
2313    character following the '*' (if any), and the matched sequence(s) are
2314    copied into star_match.
2315    */
2316 int
2317 looking_at ( char *buf, int *index, char *pattern)
2318 {
2319     char *bufp = &buf[*index], *patternp = pattern;
2320     int star_count = 0;
2321     char *matchp = star_match[0];
2322
2323     for (;;) {
2324         if (*patternp == NULLCHAR) {
2325             *index = leftover_start = bufp - buf;
2326             *matchp = NULLCHAR;
2327             return TRUE;
2328         }
2329         if (*bufp == NULLCHAR) return FALSE;
2330         if (*patternp == '*') {
2331             if (*bufp == *(patternp + 1)) {
2332                 *matchp = NULLCHAR;
2333                 matchp = star_match[++star_count];
2334                 patternp += 2;
2335                 bufp++;
2336                 continue;
2337             } else if (*bufp == '\n' || *bufp == '\r') {
2338                 patternp++;
2339                 if (*patternp == NULLCHAR)
2340                   continue;
2341                 else
2342                   return FALSE;
2343             } else {
2344                 *matchp++ = *bufp++;
2345                 continue;
2346             }
2347         }
2348         if (*patternp != *bufp) return FALSE;
2349         patternp++;
2350         bufp++;
2351     }
2352 }
2353
2354 void
2355 SendToPlayer (char *data, int length)
2356 {
2357     int error, outCount;
2358     outCount = OutputToProcess(NoProc, data, length, &error);
2359     if (outCount < length) {
2360         DisplayFatalError(_("Error writing to display"), error, 1);
2361     }
2362 }
2363
2364 void
2365 PackHolding (char packed[], char *holding)
2366 {
2367     char *p = holding;
2368     char *q = packed;
2369     int runlength = 0;
2370     int curr = 9999;
2371     do {
2372         if (*p == curr) {
2373             runlength++;
2374         } else {
2375             switch (runlength) {
2376               case 0:
2377                 break;
2378               case 1:
2379                 *q++ = curr;
2380                 break;
2381               case 2:
2382                 *q++ = curr;
2383                 *q++ = curr;
2384                 break;
2385               default:
2386                 sprintf(q, "%d", runlength);
2387                 while (*q) q++;
2388                 *q++ = curr;
2389                 break;
2390             }
2391             runlength = 1;
2392             curr = *p;
2393         }
2394     } while (*p++);
2395     *q = NULLCHAR;
2396 }
2397
2398 /* Telnet protocol requests from the front end */
2399 void
2400 TelnetRequest (unsigned char ddww, unsigned char option)
2401 {
2402     unsigned char msg[3];
2403     int outCount, outError;
2404
2405     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2406
2407     if (appData.debugMode) {
2408         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2409         switch (ddww) {
2410           case TN_DO:
2411             ddwwStr = "DO";
2412             break;
2413           case TN_DONT:
2414             ddwwStr = "DONT";
2415             break;
2416           case TN_WILL:
2417             ddwwStr = "WILL";
2418             break;
2419           case TN_WONT:
2420             ddwwStr = "WONT";
2421             break;
2422           default:
2423             ddwwStr = buf1;
2424             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2425             break;
2426         }
2427         switch (option) {
2428           case TN_ECHO:
2429             optionStr = "ECHO";
2430             break;
2431           default:
2432             optionStr = buf2;
2433             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2434             break;
2435         }
2436         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2437     }
2438     msg[0] = TN_IAC;
2439     msg[1] = ddww;
2440     msg[2] = option;
2441     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2442     if (outCount < 3) {
2443         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2444     }
2445 }
2446
2447 void
2448 DoEcho ()
2449 {
2450     if (!appData.icsActive) return;
2451     TelnetRequest(TN_DO, TN_ECHO);
2452 }
2453
2454 void
2455 DontEcho ()
2456 {
2457     if (!appData.icsActive) return;
2458     TelnetRequest(TN_DONT, TN_ECHO);
2459 }
2460
2461 void
2462 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2463 {
2464     /* put the holdings sent to us by the server on the board holdings area */
2465     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2466     char p;
2467     ChessSquare piece;
2468
2469     if(gameInfo.holdingsWidth < 2)  return;
2470     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2471         return; // prevent overwriting by pre-board holdings
2472
2473     if( (int)lowestPiece >= BlackPawn ) {
2474         holdingsColumn = 0;
2475         countsColumn = 1;
2476         holdingsStartRow = BOARD_HEIGHT-1;
2477         direction = -1;
2478     } else {
2479         holdingsColumn = BOARD_WIDTH-1;
2480         countsColumn = BOARD_WIDTH-2;
2481         holdingsStartRow = 0;
2482         direction = 1;
2483     }
2484
2485     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2486         board[i][holdingsColumn] = EmptySquare;
2487         board[i][countsColumn]   = (ChessSquare) 0;
2488     }
2489     while( (p=*holdings++) != NULLCHAR ) {
2490         piece = CharToPiece( ToUpper(p) );
2491         if(piece == EmptySquare) continue;
2492         /*j = (int) piece - (int) WhitePawn;*/
2493         j = PieceToNumber(piece);
2494         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2495         if(j < 0) continue;               /* should not happen */
2496         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2497         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2498         board[holdingsStartRow+j*direction][countsColumn]++;
2499     }
2500 }
2501
2502
2503 void
2504 VariantSwitch (Board board, VariantClass newVariant)
2505 {
2506    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2507    static Board oldBoard;
2508
2509    startedFromPositionFile = FALSE;
2510    if(gameInfo.variant == newVariant) return;
2511
2512    /* [HGM] This routine is called each time an assignment is made to
2513     * gameInfo.variant during a game, to make sure the board sizes
2514     * are set to match the new variant. If that means adding or deleting
2515     * holdings, we shift the playing board accordingly
2516     * This kludge is needed because in ICS observe mode, we get boards
2517     * of an ongoing game without knowing the variant, and learn about the
2518     * latter only later. This can be because of the move list we requested,
2519     * in which case the game history is refilled from the beginning anyway,
2520     * but also when receiving holdings of a crazyhouse game. In the latter
2521     * case we want to add those holdings to the already received position.
2522     */
2523
2524
2525    if (appData.debugMode) {
2526      fprintf(debugFP, "Switch board from %s to %s\n",
2527              VariantName(gameInfo.variant), VariantName(newVariant));
2528      setbuf(debugFP, NULL);
2529    }
2530    shuffleOpenings = 0;       /* [HGM] shuffle */
2531    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2532    switch(newVariant)
2533      {
2534      case VariantShogi:
2535        newWidth = 9;  newHeight = 9;
2536        gameInfo.holdingsSize = 7;
2537      case VariantBughouse:
2538      case VariantCrazyhouse:
2539        newHoldingsWidth = 2; break;
2540      case VariantGreat:
2541        newWidth = 10;
2542      case VariantSuper:
2543        newHoldingsWidth = 2;
2544        gameInfo.holdingsSize = 8;
2545        break;
2546      case VariantGothic:
2547      case VariantCapablanca:
2548      case VariantCapaRandom:
2549        newWidth = 10;
2550      default:
2551        newHoldingsWidth = gameInfo.holdingsSize = 0;
2552      };
2553
2554    if(newWidth  != gameInfo.boardWidth  ||
2555       newHeight != gameInfo.boardHeight ||
2556       newHoldingsWidth != gameInfo.holdingsWidth ) {
2557
2558      /* shift position to new playing area, if needed */
2559      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2560        for(i=0; i<BOARD_HEIGHT; i++)
2561          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2562            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2563              board[i][j];
2564        for(i=0; i<newHeight; i++) {
2565          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2566          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2567        }
2568      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2569        for(i=0; i<BOARD_HEIGHT; i++)
2570          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2571            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2572              board[i][j];
2573      }
2574      board[HOLDINGS_SET] = 0;
2575      gameInfo.boardWidth  = newWidth;
2576      gameInfo.boardHeight = newHeight;
2577      gameInfo.holdingsWidth = newHoldingsWidth;
2578      gameInfo.variant = newVariant;
2579      InitDrawingSizes(-2, 0);
2580    } else gameInfo.variant = newVariant;
2581    CopyBoard(oldBoard, board);   // remember correctly formatted board
2582      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2583    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2584 }
2585
2586 static int loggedOn = FALSE;
2587
2588 /*-- Game start info cache: --*/
2589 int gs_gamenum;
2590 char gs_kind[MSG_SIZ];
2591 static char player1Name[128] = "";
2592 static char player2Name[128] = "";
2593 static char cont_seq[] = "\n\\   ";
2594 static int player1Rating = -1;
2595 static int player2Rating = -1;
2596 /*----------------------------*/
2597
2598 ColorClass curColor = ColorNormal;
2599 int suppressKibitz = 0;
2600
2601 // [HGM] seekgraph
2602 Boolean soughtPending = FALSE;
2603 Boolean seekGraphUp;
2604 #define MAX_SEEK_ADS 200
2605 #define SQUARE 0x80
2606 char *seekAdList[MAX_SEEK_ADS];
2607 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2608 float tcList[MAX_SEEK_ADS];
2609 char colorList[MAX_SEEK_ADS];
2610 int nrOfSeekAds = 0;
2611 int minRating = 1010, maxRating = 2800;
2612 int hMargin = 10, vMargin = 20, h, w;
2613 extern int squareSize, lineGap;
2614
2615 void
2616 PlotSeekAd (int i)
2617 {
2618         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2619         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2620         if(r < minRating+100 && r >=0 ) r = minRating+100;
2621         if(r > maxRating) r = maxRating;
2622         if(tc < 1.f) tc = 1.f;
2623         if(tc > 95.f) tc = 95.f;
2624         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2625         y = ((double)r - minRating)/(maxRating - minRating)
2626             * (h-vMargin-squareSize/8-1) + vMargin;
2627         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2628         if(strstr(seekAdList[i], " u ")) color = 1;
2629         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2630            !strstr(seekAdList[i], "bullet") &&
2631            !strstr(seekAdList[i], "blitz") &&
2632            !strstr(seekAdList[i], "standard") ) color = 2;
2633         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2634         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2635 }
2636
2637 void
2638 PlotSingleSeekAd (int i)
2639 {
2640         PlotSeekAd(i);
2641 }
2642
2643 void
2644 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2645 {
2646         char buf[MSG_SIZ], *ext = "";
2647         VariantClass v = StringToVariant(type);
2648         if(strstr(type, "wild")) {
2649             ext = type + 4; // append wild number
2650             if(v == VariantFischeRandom) type = "chess960"; else
2651             if(v == VariantLoadable) type = "setup"; else
2652             type = VariantName(v);
2653         }
2654         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2655         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2656             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2657             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2658             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2659             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2660             seekNrList[nrOfSeekAds] = nr;
2661             zList[nrOfSeekAds] = 0;
2662             seekAdList[nrOfSeekAds++] = StrSave(buf);
2663             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2664         }
2665 }
2666
2667 void
2668 EraseSeekDot (int i)
2669 {
2670     int x = xList[i], y = yList[i], d=squareSize/4, k;
2671     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2672     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2673     // now replot every dot that overlapped
2674     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2675         int xx = xList[k], yy = yList[k];
2676         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2677             DrawSeekDot(xx, yy, colorList[k]);
2678     }
2679 }
2680
2681 void
2682 RemoveSeekAd (int nr)
2683 {
2684         int i;
2685         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2686             EraseSeekDot(i);
2687             if(seekAdList[i]) free(seekAdList[i]);
2688             seekAdList[i] = seekAdList[--nrOfSeekAds];
2689             seekNrList[i] = seekNrList[nrOfSeekAds];
2690             ratingList[i] = ratingList[nrOfSeekAds];
2691             colorList[i]  = colorList[nrOfSeekAds];
2692             tcList[i] = tcList[nrOfSeekAds];
2693             xList[i]  = xList[nrOfSeekAds];
2694             yList[i]  = yList[nrOfSeekAds];
2695             zList[i]  = zList[nrOfSeekAds];
2696             seekAdList[nrOfSeekAds] = NULL;
2697             break;
2698         }
2699 }
2700
2701 Boolean
2702 MatchSoughtLine (char *line)
2703 {
2704     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2705     int nr, base, inc, u=0; char dummy;
2706
2707     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2708        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2709        (u=1) &&
2710        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2711         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2712         // match: compact and save the line
2713         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2714         return TRUE;
2715     }
2716     return FALSE;
2717 }
2718
2719 int
2720 DrawSeekGraph ()
2721 {
2722     int i;
2723     if(!seekGraphUp) return FALSE;
2724     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2725     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2726
2727     DrawSeekBackground(0, 0, w, h);
2728     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2729     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2730     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2731         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2732         yy = h-1-yy;
2733         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2734         if(i%500 == 0) {
2735             char buf[MSG_SIZ];
2736             snprintf(buf, MSG_SIZ, "%d", i);
2737             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2738         }
2739     }
2740     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2741     for(i=1; i<100; i+=(i<10?1:5)) {
2742         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2743         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2744         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2745             char buf[MSG_SIZ];
2746             snprintf(buf, MSG_SIZ, "%d", i);
2747             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2748         }
2749     }
2750     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2751     return TRUE;
2752 }
2753
2754 int
2755 SeekGraphClick (ClickType click, int x, int y, int moving)
2756 {
2757     static int lastDown = 0, displayed = 0, lastSecond;
2758     if(y < 0) return FALSE;
2759     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2760         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2761         if(!seekGraphUp) return FALSE;
2762         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2763         DrawPosition(TRUE, NULL);
2764         return TRUE;
2765     }
2766     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2767         if(click == Release || moving) return FALSE;
2768         nrOfSeekAds = 0;
2769         soughtPending = TRUE;
2770         SendToICS(ics_prefix);
2771         SendToICS("sought\n"); // should this be "sought all"?
2772     } else { // issue challenge based on clicked ad
2773         int dist = 10000; int i, closest = 0, second = 0;
2774         for(i=0; i<nrOfSeekAds; i++) {
2775             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2776             if(d < dist) { dist = d; closest = i; }
2777             second += (d - zList[i] < 120); // count in-range ads
2778             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2779         }
2780         if(dist < 120) {
2781             char buf[MSG_SIZ];
2782             second = (second > 1);
2783             if(displayed != closest || second != lastSecond) {
2784                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2785                 lastSecond = second; displayed = closest;
2786             }
2787             if(click == Press) {
2788                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2789                 lastDown = closest;
2790                 return TRUE;
2791             } // on press 'hit', only show info
2792             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2793             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2794             SendToICS(ics_prefix);
2795             SendToICS(buf);
2796             return TRUE; // let incoming board of started game pop down the graph
2797         } else if(click == Release) { // release 'miss' is ignored
2798             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2799             if(moving == 2) { // right up-click
2800                 nrOfSeekAds = 0; // refresh graph
2801                 soughtPending = TRUE;
2802                 SendToICS(ics_prefix);
2803                 SendToICS("sought\n"); // should this be "sought all"?
2804             }
2805             return TRUE;
2806         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2807         // press miss or release hit 'pop down' seek graph
2808         seekGraphUp = FALSE;
2809         DrawPosition(TRUE, NULL);
2810     }
2811     return TRUE;
2812 }
2813
2814 void
2815 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2816 {
2817 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2818 #define STARTED_NONE 0
2819 #define STARTED_MOVES 1
2820 #define STARTED_BOARD 2
2821 #define STARTED_OBSERVE 3
2822 #define STARTED_HOLDINGS 4
2823 #define STARTED_CHATTER 5
2824 #define STARTED_COMMENT 6
2825 #define STARTED_MOVES_NOHIDE 7
2826
2827     static int started = STARTED_NONE;
2828     static char parse[20000];
2829     static int parse_pos = 0;
2830     static char buf[BUF_SIZE + 1];
2831     static int firstTime = TRUE, intfSet = FALSE;
2832     static ColorClass prevColor = ColorNormal;
2833     static int savingComment = FALSE;
2834     static int cmatch = 0; // continuation sequence match
2835     char *bp;
2836     char str[MSG_SIZ];
2837     int i, oldi;
2838     int buf_len;
2839     int next_out;
2840     int tkind;
2841     int backup;    /* [DM] For zippy color lines */
2842     char *p;
2843     char talker[MSG_SIZ]; // [HGM] chat
2844     int channel, collective=0;
2845
2846     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2847
2848     if (appData.debugMode) {
2849       if (!error) {
2850         fprintf(debugFP, "<ICS: ");
2851         show_bytes(debugFP, data, count);
2852         fprintf(debugFP, "\n");
2853       }
2854     }
2855
2856     if (appData.debugMode) { int f = forwardMostMove;
2857         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2858                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2859                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2860     }
2861     if (count > 0) {
2862         /* If last read ended with a partial line that we couldn't parse,
2863            prepend it to the new read and try again. */
2864         if (leftover_len > 0) {
2865             for (i=0; i<leftover_len; i++)
2866               buf[i] = buf[leftover_start + i];
2867         }
2868
2869     /* copy new characters into the buffer */
2870     bp = buf + leftover_len;
2871     buf_len=leftover_len;
2872     for (i=0; i<count; i++)
2873     {
2874         // ignore these
2875         if (data[i] == '\r')
2876             continue;
2877
2878         // join lines split by ICS?
2879         if (!appData.noJoin)
2880         {
2881             /*
2882                 Joining just consists of finding matches against the
2883                 continuation sequence, and discarding that sequence
2884                 if found instead of copying it.  So, until a match
2885                 fails, there's nothing to do since it might be the
2886                 complete sequence, and thus, something we don't want
2887                 copied.
2888             */
2889             if (data[i] == cont_seq[cmatch])
2890             {
2891                 cmatch++;
2892                 if (cmatch == strlen(cont_seq))
2893                 {
2894                     cmatch = 0; // complete match.  just reset the counter
2895
2896                     /*
2897                         it's possible for the ICS to not include the space
2898                         at the end of the last word, making our [correct]
2899                         join operation fuse two separate words.  the server
2900                         does this when the space occurs at the width setting.
2901                     */
2902                     if (!buf_len || buf[buf_len-1] != ' ')
2903                     {
2904                         *bp++ = ' ';
2905                         buf_len++;
2906                     }
2907                 }
2908                 continue;
2909             }
2910             else if (cmatch)
2911             {
2912                 /*
2913                     match failed, so we have to copy what matched before
2914                     falling through and copying this character.  In reality,
2915                     this will only ever be just the newline character, but
2916                     it doesn't hurt to be precise.
2917                 */
2918                 strncpy(bp, cont_seq, cmatch);
2919                 bp += cmatch;
2920                 buf_len += cmatch;
2921                 cmatch = 0;
2922             }
2923         }
2924
2925         // copy this char
2926         *bp++ = data[i];
2927         buf_len++;
2928     }
2929
2930         buf[buf_len] = NULLCHAR;
2931 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2932         next_out = 0;
2933         leftover_start = 0;
2934
2935         i = 0;
2936         while (i < buf_len) {
2937             /* Deal with part of the TELNET option negotiation
2938                protocol.  We refuse to do anything beyond the
2939                defaults, except that we allow the WILL ECHO option,
2940                which ICS uses to turn off password echoing when we are
2941                directly connected to it.  We reject this option
2942                if localLineEditing mode is on (always on in xboard)
2943                and we are talking to port 23, which might be a real
2944                telnet server that will try to keep WILL ECHO on permanently.
2945              */
2946             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2947                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2948                 unsigned char option;
2949                 oldi = i;
2950                 switch ((unsigned char) buf[++i]) {
2951                   case TN_WILL:
2952                     if (appData.debugMode)
2953                       fprintf(debugFP, "\n<WILL ");
2954                     switch (option = (unsigned char) buf[++i]) {
2955                       case TN_ECHO:
2956                         if (appData.debugMode)
2957                           fprintf(debugFP, "ECHO ");
2958                         /* Reply only if this is a change, according
2959                            to the protocol rules. */
2960                         if (remoteEchoOption) break;
2961                         if (appData.localLineEditing &&
2962                             atoi(appData.icsPort) == TN_PORT) {
2963                             TelnetRequest(TN_DONT, TN_ECHO);
2964                         } else {
2965                             EchoOff();
2966                             TelnetRequest(TN_DO, TN_ECHO);
2967                             remoteEchoOption = TRUE;
2968                         }
2969                         break;
2970                       default:
2971                         if (appData.debugMode)
2972                           fprintf(debugFP, "%d ", option);
2973                         /* Whatever this is, we don't want it. */
2974                         TelnetRequest(TN_DONT, option);
2975                         break;
2976                     }
2977                     break;
2978                   case TN_WONT:
2979                     if (appData.debugMode)
2980                       fprintf(debugFP, "\n<WONT ");
2981                     switch (option = (unsigned char) buf[++i]) {
2982                       case TN_ECHO:
2983                         if (appData.debugMode)
2984                           fprintf(debugFP, "ECHO ");
2985                         /* Reply only if this is a change, according
2986                            to the protocol rules. */
2987                         if (!remoteEchoOption) break;
2988                         EchoOn();
2989                         TelnetRequest(TN_DONT, TN_ECHO);
2990                         remoteEchoOption = FALSE;
2991                         break;
2992                       default:
2993                         if (appData.debugMode)
2994                           fprintf(debugFP, "%d ", (unsigned char) option);
2995                         /* Whatever this is, it must already be turned
2996                            off, because we never agree to turn on
2997                            anything non-default, so according to the
2998                            protocol rules, we don't reply. */
2999                         break;
3000                     }
3001                     break;
3002                   case TN_DO:
3003                     if (appData.debugMode)
3004                       fprintf(debugFP, "\n<DO ");
3005                     switch (option = (unsigned char) buf[++i]) {
3006                       default:
3007                         /* Whatever this is, we refuse to do it. */
3008                         if (appData.debugMode)
3009                           fprintf(debugFP, "%d ", option);
3010                         TelnetRequest(TN_WONT, option);
3011                         break;
3012                     }
3013                     break;
3014                   case TN_DONT:
3015                     if (appData.debugMode)
3016                       fprintf(debugFP, "\n<DONT ");
3017                     switch (option = (unsigned char) buf[++i]) {
3018                       default:
3019                         if (appData.debugMode)
3020                           fprintf(debugFP, "%d ", option);
3021                         /* Whatever this is, we are already not doing
3022                            it, because we never agree to do anything
3023                            non-default, so according to the protocol
3024                            rules, we don't reply. */
3025                         break;
3026                     }
3027                     break;
3028                   case TN_IAC:
3029                     if (appData.debugMode)
3030                       fprintf(debugFP, "\n<IAC ");
3031                     /* Doubled IAC; pass it through */
3032                     i--;
3033                     break;
3034                   default:
3035                     if (appData.debugMode)
3036                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3037                     /* Drop all other telnet commands on the floor */
3038                     break;
3039                 }
3040                 if (oldi > next_out)
3041                   SendToPlayer(&buf[next_out], oldi - next_out);
3042                 if (++i > next_out)
3043                   next_out = i;
3044                 continue;
3045             }
3046
3047             /* OK, this at least will *usually* work */
3048             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3049                 loggedOn = TRUE;
3050             }
3051
3052             if (loggedOn && !intfSet) {
3053                 if (ics_type == ICS_ICC) {
3054                   snprintf(str, MSG_SIZ,
3055                           "/set-quietly interface %s\n/set-quietly style 12\n",
3056                           programVersion);
3057                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3058                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3059                 } else if (ics_type == ICS_CHESSNET) {
3060                   snprintf(str, MSG_SIZ, "/style 12\n");
3061                 } else {
3062                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3063                   strcat(str, programVersion);
3064                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3065                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3066                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3067 #ifdef WIN32
3068                   strcat(str, "$iset nohighlight 1\n");
3069 #endif
3070                   strcat(str, "$iset lock 1\n$style 12\n");
3071                 }
3072                 SendToICS(str);
3073                 NotifyFrontendLogin();
3074                 intfSet = TRUE;
3075             }
3076
3077             if (started == STARTED_COMMENT) {
3078                 /* Accumulate characters in comment */
3079                 parse[parse_pos++] = buf[i];
3080                 if (buf[i] == '\n') {
3081                     parse[parse_pos] = NULLCHAR;
3082                     if(chattingPartner>=0) {
3083                         char mess[MSG_SIZ];
3084                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3085                         OutputChatMessage(chattingPartner, mess);
3086                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3087                             int p;
3088                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3089                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3090                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3091                                 OutputChatMessage(p, mess);
3092                                 break;
3093                             }
3094                         }
3095                         chattingPartner = -1;
3096                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3097                         collective = 0;
3098                     } else
3099                     if(!suppressKibitz) // [HGM] kibitz
3100                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3101                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3102                         int nrDigit = 0, nrAlph = 0, j;
3103                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3104                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3105                         parse[parse_pos] = NULLCHAR;
3106                         // try to be smart: if it does not look like search info, it should go to
3107                         // ICS interaction window after all, not to engine-output window.
3108                         for(j=0; j<parse_pos; j++) { // count letters and digits
3109                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3110                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3111                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3112                         }
3113                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3114                             int depth=0; float score;
3115                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3116                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3117                                 pvInfoList[forwardMostMove-1].depth = depth;
3118                                 pvInfoList[forwardMostMove-1].score = 100*score;
3119                             }
3120                             OutputKibitz(suppressKibitz, parse);
3121                         } else {
3122                             char tmp[MSG_SIZ];
3123                             if(gameMode == IcsObserving) // restore original ICS messages
3124                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3125                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3126                             else
3127                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3128                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3129                             SendToPlayer(tmp, strlen(tmp));
3130                         }
3131                         next_out = i+1; // [HGM] suppress printing in ICS window
3132                     }
3133                     started = STARTED_NONE;
3134                 } else {
3135                     /* Don't match patterns against characters in comment */
3136                     i++;
3137                     continue;
3138                 }
3139             }
3140             if (started == STARTED_CHATTER) {
3141                 if (buf[i] != '\n') {
3142                     /* Don't match patterns against characters in chatter */
3143                     i++;
3144                     continue;
3145                 }
3146                 started = STARTED_NONE;
3147                 if(suppressKibitz) next_out = i+1;
3148             }
3149
3150             /* Kludge to deal with rcmd protocol */
3151             if (firstTime && looking_at(buf, &i, "\001*")) {
3152                 DisplayFatalError(&buf[1], 0, 1);
3153                 continue;
3154             } else {
3155                 firstTime = FALSE;
3156             }
3157
3158             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3159                 ics_type = ICS_ICC;
3160                 ics_prefix = "/";
3161                 if (appData.debugMode)
3162                   fprintf(debugFP, "ics_type %d\n", ics_type);
3163                 continue;
3164             }
3165             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3166                 ics_type = ICS_FICS;
3167                 ics_prefix = "$";
3168                 if (appData.debugMode)
3169                   fprintf(debugFP, "ics_type %d\n", ics_type);
3170                 continue;
3171             }
3172             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3173                 ics_type = ICS_CHESSNET;
3174                 ics_prefix = "/";
3175                 if (appData.debugMode)
3176                   fprintf(debugFP, "ics_type %d\n", ics_type);
3177                 continue;
3178             }
3179
3180             if (!loggedOn &&
3181                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3182                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3183                  looking_at(buf, &i, "will be \"*\""))) {
3184               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3185               continue;
3186             }
3187
3188             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3189               char buf[MSG_SIZ];
3190               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3191               DisplayIcsInteractionTitle(buf);
3192               have_set_title = TRUE;
3193             }
3194
3195             /* skip finger notes */
3196             if (started == STARTED_NONE &&
3197                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3198                  (buf[i] == '1' && buf[i+1] == '0')) &&
3199                 buf[i+2] == ':' && buf[i+3] == ' ') {
3200               started = STARTED_CHATTER;
3201               i += 3;
3202               continue;
3203             }
3204
3205             oldi = i;
3206             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3207             if(appData.seekGraph) {
3208                 if(soughtPending && MatchSoughtLine(buf+i)) {
3209                     i = strstr(buf+i, "rated") - buf;
3210                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3211                     next_out = leftover_start = i;
3212                     started = STARTED_CHATTER;
3213                     suppressKibitz = TRUE;
3214                     continue;
3215                 }
3216                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3217                         && looking_at(buf, &i, "* ads displayed")) {
3218                     soughtPending = FALSE;
3219                     seekGraphUp = TRUE;
3220                     DrawSeekGraph();
3221                     continue;
3222                 }
3223                 if(appData.autoRefresh) {
3224                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3225                         int s = (ics_type == ICS_ICC); // ICC format differs
3226                         if(seekGraphUp)
3227                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3228                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3229                         looking_at(buf, &i, "*% "); // eat prompt
3230                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3231                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3232                         next_out = i; // suppress
3233                         continue;
3234                     }
3235                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3236                         char *p = star_match[0];
3237                         while(*p) {
3238                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3239                             while(*p && *p++ != ' '); // next
3240                         }
3241                         looking_at(buf, &i, "*% "); // eat prompt
3242                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3243                         next_out = i;
3244                         continue;
3245                     }
3246                 }
3247             }
3248
3249             /* skip formula vars */
3250             if (started == STARTED_NONE &&
3251                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3252               started = STARTED_CHATTER;
3253               i += 3;
3254               continue;
3255             }
3256
3257             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3258             if (appData.autoKibitz && started == STARTED_NONE &&
3259                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3260                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3261                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3262                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3263                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3264                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3265                         suppressKibitz = TRUE;
3266                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3267                         next_out = i;
3268                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3269                                 && (gameMode == IcsPlayingWhite)) ||
3270                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3271                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3272                             started = STARTED_CHATTER; // own kibitz we simply discard
3273                         else {
3274                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3275                             parse_pos = 0; parse[0] = NULLCHAR;
3276                             savingComment = TRUE;
3277                             suppressKibitz = gameMode != IcsObserving ? 2 :
3278                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3279                         }
3280                         continue;
3281                 } else
3282                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3283                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3284                          && atoi(star_match[0])) {
3285                     // suppress the acknowledgements of our own autoKibitz
3286                     char *p;
3287                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3288                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3289                     SendToPlayer(star_match[0], strlen(star_match[0]));
3290                     if(looking_at(buf, &i, "*% ")) // eat prompt
3291                         suppressKibitz = FALSE;
3292                     next_out = i;
3293                     continue;
3294                 }
3295             } // [HGM] kibitz: end of patch
3296
3297             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3298
3299             // [HGM] chat: intercept tells by users for which we have an open chat window
3300             channel = -1;
3301             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3302                                            looking_at(buf, &i, "* whispers:") ||
3303                                            looking_at(buf, &i, "* kibitzes:") ||
3304                                            looking_at(buf, &i, "* shouts:") ||
3305                                            looking_at(buf, &i, "* c-shouts:") ||
3306                                            looking_at(buf, &i, "--> * ") ||
3307                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3308                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3309                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3310                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3311                 int p;
3312                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3313                 chattingPartner = -1; collective = 0;
3314
3315                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3316                 for(p=0; p<MAX_CHAT; p++) {
3317                     collective = 1;
3318                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3319                     talker[0] = '['; strcat(talker, "] ");
3320                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3321                     chattingPartner = p; break;
3322                     }
3323                 } else
3324                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3325                 for(p=0; p<MAX_CHAT; p++) {
3326                     collective = 1;
3327                     if(!strcmp("kibitzes", chatPartner[p])) {
3328                         talker[0] = '['; strcat(talker, "] ");
3329                         chattingPartner = p; break;
3330                     }
3331                 } else
3332                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3333                 for(p=0; p<MAX_CHAT; p++) {
3334                     collective = 1;
3335                     if(!strcmp("whispers", chatPartner[p])) {
3336                         talker[0] = '['; strcat(talker, "] ");
3337                         chattingPartner = p; break;
3338                     }
3339                 } else
3340                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3341                   if(buf[i-8] == '-' && buf[i-3] == 't')
3342                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3343                     collective = 1;
3344                     if(!strcmp("c-shouts", chatPartner[p])) {
3345                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3346                         chattingPartner = p; break;
3347                     }
3348                   }
3349                   if(chattingPartner < 0)
3350                   for(p=0; p<MAX_CHAT; p++) {
3351                     collective = 1;
3352                     if(!strcmp("shouts", chatPartner[p])) {
3353                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3354                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3355                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3356                         chattingPartner = p; break;
3357                     }
3358                   }
3359                 }
3360                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3361                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3362                     talker[0] = 0;
3363                     Colorize(ColorTell, FALSE);
3364                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3365                     collective |= 2;
3366                     chattingPartner = p; break;
3367                 }
3368                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3369                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3370                     started = STARTED_COMMENT;
3371                     parse_pos = 0; parse[0] = NULLCHAR;
3372                     savingComment = 3 + chattingPartner; // counts as TRUE
3373                     if(collective == 3) i = oldi; else {
3374                         suppressKibitz = TRUE;
3375                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3376                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3377                         continue;
3378                     }
3379                 }
3380             } // [HGM] chat: end of patch
3381
3382           backup = i;
3383             if (appData.zippyTalk || appData.zippyPlay) {
3384                 /* [DM] Backup address for color zippy lines */
3385 #if ZIPPY
3386                if (loggedOn == TRUE)
3387                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3388                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3389 #endif
3390             } // [DM] 'else { ' deleted
3391                 if (
3392                     /* Regular tells and says */
3393                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3394                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3395                     looking_at(buf, &i, "* says: ") ||
3396                     /* Don't color "message" or "messages" output */
3397                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3398                     looking_at(buf, &i, "*. * at *:*: ") ||
3399                     looking_at(buf, &i, "--* (*:*): ") ||
3400                     /* Message notifications (same color as tells) */
3401                     looking_at(buf, &i, "* has left a message ") ||
3402                     looking_at(buf, &i, "* just sent you a message:\n") ||
3403                     /* Whispers and kibitzes */
3404                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3405                     looking_at(buf, &i, "* kibitzes: ") ||
3406                     /* Channel tells */
3407                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3408
3409                   if (tkind == 1 && strchr(star_match[0], ':')) {
3410                       /* Avoid "tells you:" spoofs in channels */
3411                      tkind = 3;
3412                   }
3413                   if (star_match[0][0] == NULLCHAR ||
3414                       strchr(star_match[0], ' ') ||
3415                       (tkind == 3 && strchr(star_match[1], ' '))) {
3416                     /* Reject bogus matches */
3417                     i = oldi;
3418                   } else {
3419                     if (appData.colorize) {
3420                       if (oldi > next_out) {
3421                         SendToPlayer(&buf[next_out], oldi - next_out);
3422                         next_out = oldi;
3423                       }
3424                       switch (tkind) {
3425                       case 1:
3426                         Colorize(ColorTell, FALSE);
3427                         curColor = ColorTell;
3428                         break;
3429                       case 2:
3430                         Colorize(ColorKibitz, FALSE);
3431                         curColor = ColorKibitz;
3432                         break;
3433                       case 3:
3434                         p = strrchr(star_match[1], '(');
3435                         if (p == NULL) {
3436                           p = star_match[1];
3437                         } else {
3438                           p++;
3439                         }
3440                         if (atoi(p) == 1) {
3441                           Colorize(ColorChannel1, FALSE);
3442                           curColor = ColorChannel1;
3443                         } else {
3444                           Colorize(ColorChannel, FALSE);
3445                           curColor = ColorChannel;
3446                         }
3447                         break;
3448                       case 5:
3449                         curColor = ColorNormal;
3450                         break;
3451                       }
3452                     }
3453                     if (started == STARTED_NONE && appData.autoComment &&
3454                         (gameMode == IcsObserving ||
3455                          gameMode == IcsPlayingWhite ||
3456                          gameMode == IcsPlayingBlack)) {
3457                       parse_pos = i - oldi;
3458                       memcpy(parse, &buf[oldi], parse_pos);
3459                       parse[parse_pos] = NULLCHAR;
3460                       started = STARTED_COMMENT;
3461                       savingComment = TRUE;
3462                     } else if(collective != 3) {
3463                       started = STARTED_CHATTER;
3464                       savingComment = FALSE;
3465                     }
3466                     loggedOn = TRUE;
3467                     continue;
3468                   }
3469                 }
3470
3471                 if (looking_at(buf, &i, "* s-shouts: ") ||
3472                     looking_at(buf, &i, "* c-shouts: ")) {
3473                     if (appData.colorize) {
3474                         if (oldi > next_out) {
3475                             SendToPlayer(&buf[next_out], oldi - next_out);
3476                             next_out = oldi;
3477                         }
3478                         Colorize(ColorSShout, FALSE);
3479                         curColor = ColorSShout;
3480                     }
3481                     loggedOn = TRUE;
3482                     started = STARTED_CHATTER;
3483                     continue;
3484                 }
3485
3486                 if (looking_at(buf, &i, "--->")) {
3487                     loggedOn = TRUE;
3488                     continue;
3489                 }
3490
3491                 if (looking_at(buf, &i, "* shouts: ") ||
3492                     looking_at(buf, &i, "--> ")) {
3493                     if (appData.colorize) {
3494                         if (oldi > next_out) {
3495                             SendToPlayer(&buf[next_out], oldi - next_out);
3496                             next_out = oldi;
3497                         }
3498                         Colorize(ColorShout, FALSE);
3499                         curColor = ColorShout;
3500                     }
3501                     loggedOn = TRUE;
3502                     started = STARTED_CHATTER;
3503                     continue;
3504                 }
3505
3506                 if (looking_at( buf, &i, "Challenge:")) {
3507                     if (appData.colorize) {
3508                         if (oldi > next_out) {
3509                             SendToPlayer(&buf[next_out], oldi - next_out);
3510                             next_out = oldi;
3511                         }
3512                         Colorize(ColorChallenge, FALSE);
3513                         curColor = ColorChallenge;
3514                     }
3515                     loggedOn = TRUE;
3516                     continue;
3517                 }
3518
3519                 if (looking_at(buf, &i, "* offers you") ||
3520                     looking_at(buf, &i, "* offers to be") ||
3521                     looking_at(buf, &i, "* would like to") ||
3522                     looking_at(buf, &i, "* requests to") ||
3523                     looking_at(buf, &i, "Your opponent offers") ||
3524                     looking_at(buf, &i, "Your opponent requests")) {
3525
3526                     if (appData.colorize) {
3527                         if (oldi > next_out) {
3528                             SendToPlayer(&buf[next_out], oldi - next_out);
3529                             next_out = oldi;
3530                         }
3531                         Colorize(ColorRequest, FALSE);
3532                         curColor = ColorRequest;
3533                     }
3534                     continue;
3535                 }
3536
3537                 if (looking_at(buf, &i, "* (*) seeking")) {
3538                     if (appData.colorize) {
3539                         if (oldi > next_out) {
3540                             SendToPlayer(&buf[next_out], oldi - next_out);
3541                             next_out = oldi;
3542                         }
3543                         Colorize(ColorSeek, FALSE);
3544                         curColor = ColorSeek;
3545                     }
3546                     continue;
3547             }
3548
3549           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3550
3551             if (looking_at(buf, &i, "\\   ")) {
3552                 if (prevColor != ColorNormal) {
3553                     if (oldi > next_out) {
3554                         SendToPlayer(&buf[next_out], oldi - next_out);
3555                         next_out = oldi;
3556                     }
3557                     Colorize(prevColor, TRUE);
3558                     curColor = prevColor;
3559                 }
3560                 if (savingComment) {
3561                     parse_pos = i - oldi;
3562                     memcpy(parse, &buf[oldi], parse_pos);
3563                     parse[parse_pos] = NULLCHAR;
3564                     started = STARTED_COMMENT;
3565                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3566                         chattingPartner = savingComment - 3; // kludge to remember the box
3567                 } else {
3568                     started = STARTED_CHATTER;
3569                 }
3570                 continue;
3571             }
3572
3573             if (looking_at(buf, &i, "Black Strength :") ||
3574                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3575                 looking_at(buf, &i, "<10>") ||
3576                 looking_at(buf, &i, "#@#")) {
3577                 /* Wrong board style */
3578                 loggedOn = TRUE;
3579                 SendToICS(ics_prefix);
3580                 SendToICS("set style 12\n");
3581                 SendToICS(ics_prefix);
3582                 SendToICS("refresh\n");
3583                 continue;
3584             }
3585
3586             if (looking_at(buf, &i, "login:")) {
3587               if (!have_sent_ICS_logon) {
3588                 if(ICSInitScript())
3589                   have_sent_ICS_logon = 1;
3590                 else // no init script was found
3591                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3592               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3593                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3594               }
3595                 continue;
3596             }
3597
3598             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3599                 (looking_at(buf, &i, "\n<12> ") ||
3600                  looking_at(buf, &i, "<12> "))) {
3601                 loggedOn = TRUE;
3602                 if (oldi > next_out) {
3603                     SendToPlayer(&buf[next_out], oldi - next_out);
3604                 }
3605                 next_out = i;
3606                 started = STARTED_BOARD;
3607                 parse_pos = 0;
3608                 continue;
3609             }
3610
3611             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3612                 looking_at(buf, &i, "<b1> ")) {
3613                 if (oldi > next_out) {
3614                     SendToPlayer(&buf[next_out], oldi - next_out);
3615                 }
3616                 next_out = i;
3617                 started = STARTED_HOLDINGS;
3618                 parse_pos = 0;
3619                 continue;
3620             }
3621
3622             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3623                 loggedOn = TRUE;
3624                 /* Header for a move list -- first line */
3625
3626                 switch (ics_getting_history) {
3627                   case H_FALSE:
3628                     switch (gameMode) {
3629                       case IcsIdle:
3630                       case BeginningOfGame:
3631                         /* User typed "moves" or "oldmoves" while we
3632                            were idle.  Pretend we asked for these
3633                            moves and soak them up so user can step
3634                            through them and/or save them.
3635                            */
3636                         Reset(FALSE, TRUE);
3637                         gameMode = IcsObserving;
3638                         ModeHighlight();
3639                         ics_gamenum = -1;
3640                         ics_getting_history = H_GOT_UNREQ_HEADER;
3641                         break;
3642                       case EditGame: /*?*/
3643                       case EditPosition: /*?*/
3644                         /* Should above feature work in these modes too? */
3645                         /* For now it doesn't */
3646                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3647                         break;
3648                       default:
3649                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3650                         break;
3651                     }
3652                     break;
3653                   case H_REQUESTED:
3654                     /* Is this the right one? */
3655                     if (gameInfo.white && gameInfo.black &&
3656                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3657                         strcmp(gameInfo.black, star_match[2]) == 0) {
3658                         /* All is well */
3659                         ics_getting_history = H_GOT_REQ_HEADER;
3660                     }
3661                     break;
3662                   case H_GOT_REQ_HEADER:
3663                   case H_GOT_UNREQ_HEADER:
3664                   case H_GOT_UNWANTED_HEADER:
3665                   case H_GETTING_MOVES:
3666                     /* Should not happen */
3667                     DisplayError(_("Error gathering move list: two headers"), 0);
3668                     ics_getting_history = H_FALSE;
3669                     break;
3670                 }
3671
3672                 /* Save player ratings into gameInfo if needed */
3673                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3674                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3675                     (gameInfo.whiteRating == -1 ||
3676                      gameInfo.blackRating == -1)) {
3677
3678                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3679                     gameInfo.blackRating = string_to_rating(star_match[3]);
3680                     if (appData.debugMode)
3681                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3682                               gameInfo.whiteRating, gameInfo.blackRating);
3683                 }
3684                 continue;
3685             }
3686
3687             if (looking_at(buf, &i,
3688               "* * match, initial time: * minute*, increment: * second")) {
3689                 /* Header for a move list -- second line */
3690                 /* Initial board will follow if this is a wild game */
3691                 if (gameInfo.event != NULL) free(gameInfo.event);
3692                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3693                 gameInfo.event = StrSave(str);
3694                 /* [HGM] we switched variant. Translate boards if needed. */
3695                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3696                 continue;
3697             }
3698
3699             if (looking_at(buf, &i, "Move  ")) {
3700                 /* Beginning of a move list */
3701                 switch (ics_getting_history) {
3702                   case H_FALSE:
3703                     /* Normally should not happen */
3704                     /* Maybe user hit reset while we were parsing */
3705                     break;
3706                   case H_REQUESTED:
3707                     /* Happens if we are ignoring a move list that is not
3708                      * the one we just requested.  Common if the user
3709                      * tries to observe two games without turning off
3710                      * getMoveList */
3711                     break;
3712                   case H_GETTING_MOVES:
3713                     /* Should not happen */
3714                     DisplayError(_("Error gathering move list: nested"), 0);
3715                     ics_getting_history = H_FALSE;
3716                     break;
3717                   case H_GOT_REQ_HEADER:
3718                     ics_getting_history = H_GETTING_MOVES;
3719                     started = STARTED_MOVES;
3720                     parse_pos = 0;
3721                     if (oldi > next_out) {
3722                         SendToPlayer(&buf[next_out], oldi - next_out);
3723                     }
3724                     break;
3725                   case H_GOT_UNREQ_HEADER:
3726                     ics_getting_history = H_GETTING_MOVES;
3727                     started = STARTED_MOVES_NOHIDE;
3728                     parse_pos = 0;
3729                     break;
3730                   case H_GOT_UNWANTED_HEADER:
3731                     ics_getting_history = H_FALSE;
3732                     break;
3733                 }
3734                 continue;
3735             }
3736
3737             if (looking_at(buf, &i, "% ") ||
3738                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3739                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3740                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3741                     soughtPending = FALSE;
3742                     seekGraphUp = TRUE;
3743                     DrawSeekGraph();
3744                 }
3745                 if(suppressKibitz) next_out = i;
3746                 savingComment = FALSE;
3747                 suppressKibitz = 0;
3748                 switch (started) {
3749                   case STARTED_MOVES:
3750                   case STARTED_MOVES_NOHIDE:
3751                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3752                     parse[parse_pos + i - oldi] = NULLCHAR;
3753                     ParseGameHistory(parse);
3754 #if ZIPPY
3755                     if (appData.zippyPlay && first.initDone) {
3756                         FeedMovesToProgram(&first, forwardMostMove);
3757                         if (gameMode == IcsPlayingWhite) {
3758                             if (WhiteOnMove(forwardMostMove)) {
3759                                 if (first.sendTime) {
3760                                   if (first.useColors) {
3761                                     SendToProgram("black\n", &first);
3762                                   }
3763                                   SendTimeRemaining(&first, TRUE);
3764                                 }
3765                                 if (first.useColors) {
3766                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3767                                 }
3768                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3769                                 first.maybeThinking = TRUE;
3770                             } else {
3771                                 if (first.usePlayother) {
3772                                   if (first.sendTime) {
3773                                     SendTimeRemaining(&first, TRUE);
3774                                   }
3775                                   SendToProgram("playother\n", &first);
3776                                   firstMove = FALSE;
3777                                 } else {
3778                                   firstMove = TRUE;
3779                                 }
3780                             }
3781                         } else if (gameMode == IcsPlayingBlack) {
3782                             if (!WhiteOnMove(forwardMostMove)) {
3783                                 if (first.sendTime) {
3784                                   if (first.useColors) {
3785                                     SendToProgram("white\n", &first);
3786                                   }
3787                                   SendTimeRemaining(&first, FALSE);
3788                                 }
3789                                 if (first.useColors) {
3790                                   SendToProgram("black\n", &first);
3791                                 }
3792                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3793                                 first.maybeThinking = TRUE;
3794                             } else {
3795                                 if (first.usePlayother) {
3796                                   if (first.sendTime) {
3797                                     SendTimeRemaining(&first, FALSE);
3798                                   }
3799                                   SendToProgram("playother\n", &first);
3800                                   firstMove = FALSE;
3801                                 } else {
3802                                   firstMove = TRUE;
3803                                 }
3804                             }
3805                         }
3806                     }
3807 #endif
3808                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3809                         /* Moves came from oldmoves or moves command
3810                            while we weren't doing anything else.
3811                            */
3812                         currentMove = forwardMostMove;
3813                         ClearHighlights();/*!!could figure this out*/
3814                         flipView = appData.flipView;
3815                         DrawPosition(TRUE, boards[currentMove]);
3816                         DisplayBothClocks();
3817                         snprintf(str, MSG_SIZ, "%s %s %s",
3818                                 gameInfo.white, _("vs."),  gameInfo.black);
3819                         DisplayTitle(str);
3820                         gameMode = IcsIdle;
3821                     } else {
3822                         /* Moves were history of an active game */
3823                         if (gameInfo.resultDetails != NULL) {
3824                             free(gameInfo.resultDetails);
3825                             gameInfo.resultDetails = NULL;
3826                         }
3827                     }
3828                     HistorySet(parseList, backwardMostMove,
3829                                forwardMostMove, currentMove-1);
3830                     DisplayMove(currentMove - 1);
3831                     if (started == STARTED_MOVES) next_out = i;
3832                     started = STARTED_NONE;
3833                     ics_getting_history = H_FALSE;
3834                     break;
3835
3836                   case STARTED_OBSERVE:
3837                     started = STARTED_NONE;
3838                     SendToICS(ics_prefix);
3839                     SendToICS("refresh\n");
3840                     break;
3841
3842                   default:
3843                     break;
3844                 }
3845                 if(bookHit) { // [HGM] book: simulate book reply
3846                     static char bookMove[MSG_SIZ]; // a bit generous?
3847
3848                     programStats.nodes = programStats.depth = programStats.time =
3849                     programStats.score = programStats.got_only_move = 0;
3850                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3851
3852                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3853                     strcat(bookMove, bookHit);
3854                     HandleMachineMove(bookMove, &first);
3855                 }
3856                 continue;
3857             }
3858
3859             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3860                  started == STARTED_HOLDINGS ||
3861                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3862                 /* Accumulate characters in move list or board */
3863                 parse[parse_pos++] = buf[i];
3864             }
3865
3866             /* Start of game messages.  Mostly we detect start of game
3867                when the first board image arrives.  On some versions
3868                of the ICS, though, we need to do a "refresh" after starting
3869                to observe in order to get the current board right away. */
3870             if (looking_at(buf, &i, "Adding game * to observation list")) {
3871                 started = STARTED_OBSERVE;
3872                 continue;
3873             }
3874
3875             /* Handle auto-observe */
3876             if (appData.autoObserve &&
3877                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3878                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3879                 char *player;
3880                 /* Choose the player that was highlighted, if any. */
3881                 if (star_match[0][0] == '\033' ||
3882                     star_match[1][0] != '\033') {
3883                     player = star_match[0];
3884                 } else {
3885                     player = star_match[2];
3886                 }
3887                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3888                         ics_prefix, StripHighlightAndTitle(player));
3889                 SendToICS(str);
3890
3891                 /* Save ratings from notify string */
3892                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3893                 player1Rating = string_to_rating(star_match[1]);
3894                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3895                 player2Rating = string_to_rating(star_match[3]);
3896
3897                 if (appData.debugMode)
3898                   fprintf(debugFP,
3899                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3900                           player1Name, player1Rating,
3901                           player2Name, player2Rating);
3902
3903                 continue;
3904             }
3905
3906             /* Deal with automatic examine mode after a game,
3907                and with IcsObserving -> IcsExamining transition */
3908             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3909                 looking_at(buf, &i, "has made you an examiner of game *")) {
3910
3911                 int gamenum = atoi(star_match[0]);
3912                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3913                     gamenum == ics_gamenum) {
3914                     /* We were already playing or observing this game;
3915                        no need to refetch history */
3916                     gameMode = IcsExamining;
3917                     if (pausing) {
3918                         pauseExamForwardMostMove = forwardMostMove;
3919                     } else if (currentMove < forwardMostMove) {
3920                         ForwardInner(forwardMostMove);
3921                     }
3922                 } else {
3923                     /* I don't think this case really can happen */
3924                     SendToICS(ics_prefix);
3925                     SendToICS("refresh\n");
3926                 }
3927                 continue;
3928             }
3929
3930             /* Error messages */
3931 //          if (ics_user_moved) {
3932             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3933                 if (looking_at(buf, &i, "Illegal move") ||
3934                     looking_at(buf, &i, "Not a legal move") ||
3935                     looking_at(buf, &i, "Your king is in check") ||
3936                     looking_at(buf, &i, "It isn't your turn") ||
3937                     looking_at(buf, &i, "It is not your move")) {
3938                     /* Illegal move */
3939                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3940                         currentMove = forwardMostMove-1;
3941                         DisplayMove(currentMove - 1); /* before DMError */
3942                         DrawPosition(FALSE, boards[currentMove]);
3943                         SwitchClocks(forwardMostMove-1); // [HGM] race
3944                         DisplayBothClocks();
3945                     }
3946                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3947                     ics_user_moved = 0;
3948                     continue;
3949                 }
3950             }
3951
3952             if (looking_at(buf, &i, "still have time") ||
3953                 looking_at(buf, &i, "not out of time") ||
3954                 looking_at(buf, &i, "either player is out of time") ||
3955                 looking_at(buf, &i, "has timeseal; checking")) {
3956                 /* We must have called his flag a little too soon */
3957                 whiteFlag = blackFlag = FALSE;
3958                 continue;
3959             }
3960
3961             if (looking_at(buf, &i, "added * seconds to") ||
3962                 looking_at(buf, &i, "seconds were added to")) {
3963                 /* Update the clocks */
3964                 SendToICS(ics_prefix);
3965                 SendToICS("refresh\n");
3966                 continue;
3967             }
3968
3969             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3970                 ics_clock_paused = TRUE;
3971                 StopClocks();
3972                 continue;
3973             }
3974
3975             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3976                 ics_clock_paused = FALSE;
3977                 StartClocks();
3978                 continue;
3979             }
3980
3981             /* Grab player ratings from the Creating: message.
3982                Note we have to check for the special case when
3983                the ICS inserts things like [white] or [black]. */
3984             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3985                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3986                 /* star_matches:
3987                    0    player 1 name (not necessarily white)
3988                    1    player 1 rating
3989                    2    empty, white, or black (IGNORED)
3990                    3    player 2 name (not necessarily black)
3991                    4    player 2 rating
3992
3993                    The names/ratings are sorted out when the game
3994                    actually starts (below).
3995                 */
3996                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3997                 player1Rating = string_to_rating(star_match[1]);
3998                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3999                 player2Rating = string_to_rating(star_match[4]);
4000
4001                 if (appData.debugMode)
4002                   fprintf(debugFP,
4003                           "Ratings from 'Creating:' %s %d, %s %d\n",
4004                           player1Name, player1Rating,
4005                           player2Name, player2Rating);
4006
4007                 continue;
4008             }
4009
4010             /* Improved generic start/end-of-game messages */
4011             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4012                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4013                 /* If tkind == 0: */
4014                 /* star_match[0] is the game number */
4015                 /*           [1] is the white player's name */
4016                 /*           [2] is the black player's name */
4017                 /* For end-of-game: */
4018                 /*           [3] is the reason for the game end */
4019                 /*           [4] is a PGN end game-token, preceded by " " */
4020                 /* For start-of-game: */
4021                 /*           [3] begins with "Creating" or "Continuing" */
4022                 /*           [4] is " *" or empty (don't care). */
4023                 int gamenum = atoi(star_match[0]);
4024                 char *whitename, *blackname, *why, *endtoken;
4025                 ChessMove endtype = EndOfFile;
4026
4027                 if (tkind == 0) {
4028                   whitename = star_match[1];
4029                   blackname = star_match[2];
4030                   why = star_match[3];
4031                   endtoken = star_match[4];
4032                 } else {
4033                   whitename = star_match[1];
4034                   blackname = star_match[3];
4035                   why = star_match[5];
4036                   endtoken = star_match[6];
4037                 }
4038
4039                 /* Game start messages */
4040                 if (strncmp(why, "Creating ", 9) == 0 ||
4041                     strncmp(why, "Continuing ", 11) == 0) {
4042                     gs_gamenum = gamenum;
4043                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4044                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4045                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4046 #if ZIPPY
4047                     if (appData.zippyPlay) {
4048                         ZippyGameStart(whitename, blackname);
4049                     }
4050 #endif /*ZIPPY*/
4051                     partnerBoardValid = FALSE; // [HGM] bughouse
4052                     continue;
4053                 }
4054
4055                 /* Game end messages */
4056                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4057                     ics_gamenum != gamenum) {
4058                     continue;
4059                 }
4060                 while (endtoken[0] == ' ') endtoken++;
4061                 switch (endtoken[0]) {
4062                   case '*':
4063                   default:
4064                     endtype = GameUnfinished;
4065                     break;
4066                   case '0':
4067                     endtype = BlackWins;
4068                     break;
4069                   case '1':
4070                     if (endtoken[1] == '/')
4071                       endtype = GameIsDrawn;
4072                     else
4073                       endtype = WhiteWins;
4074                     break;
4075                 }
4076                 GameEnds(endtype, why, GE_ICS);
4077 #if ZIPPY
4078                 if (appData.zippyPlay && first.initDone) {
4079                     ZippyGameEnd(endtype, why);
4080                     if (first.pr == NoProc) {
4081                       /* Start the next process early so that we'll
4082                          be ready for the next challenge */
4083                       StartChessProgram(&first);
4084                     }
4085                     /* Send "new" early, in case this command takes
4086                        a long time to finish, so that we'll be ready
4087                        for the next challenge. */
4088                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4089                     Reset(TRUE, TRUE);
4090                 }
4091 #endif /*ZIPPY*/
4092                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4093                 continue;
4094             }
4095
4096             if (looking_at(buf, &i, "Removing game * from observation") ||
4097                 looking_at(buf, &i, "no longer observing game *") ||
4098                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4099                 if (gameMode == IcsObserving &&
4100                     atoi(star_match[0]) == ics_gamenum)
4101                   {
4102                       /* icsEngineAnalyze */
4103                       if (appData.icsEngineAnalyze) {
4104                             ExitAnalyzeMode();
4105                             ModeHighlight();
4106                       }
4107                       StopClocks();
4108                       gameMode = IcsIdle;
4109                       ics_gamenum = -1;
4110                       ics_user_moved = FALSE;
4111                   }
4112                 continue;
4113             }
4114
4115             if (looking_at(buf, &i, "no longer examining game *")) {
4116                 if (gameMode == IcsExamining &&
4117                     atoi(star_match[0]) == ics_gamenum)
4118                   {
4119                       gameMode = IcsIdle;
4120                       ics_gamenum = -1;
4121                       ics_user_moved = FALSE;
4122                   }
4123                 continue;
4124             }
4125
4126             /* Advance leftover_start past any newlines we find,
4127                so only partial lines can get reparsed */
4128             if (looking_at(buf, &i, "\n")) {
4129                 prevColor = curColor;
4130                 if (curColor != ColorNormal) {
4131                     if (oldi > next_out) {
4132                         SendToPlayer(&buf[next_out], oldi - next_out);
4133                         next_out = oldi;
4134                     }
4135                     Colorize(ColorNormal, FALSE);
4136                     curColor = ColorNormal;
4137                 }
4138                 if (started == STARTED_BOARD) {
4139                     started = STARTED_NONE;
4140                     parse[parse_pos] = NULLCHAR;
4141                     ParseBoard12(parse);
4142                     ics_user_moved = 0;
4143
4144                     /* Send premove here */
4145                     if (appData.premove) {
4146                       char str[MSG_SIZ];
4147                       if (currentMove == 0 &&
4148                           gameMode == IcsPlayingWhite &&
4149                           appData.premoveWhite) {
4150                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4151                         if (appData.debugMode)
4152                           fprintf(debugFP, "Sending premove:\n");
4153                         SendToICS(str);
4154                       } else if (currentMove == 1 &&
4155                                  gameMode == IcsPlayingBlack &&
4156                                  appData.premoveBlack) {
4157                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4158                         if (appData.debugMode)
4159                           fprintf(debugFP, "Sending premove:\n");
4160                         SendToICS(str);
4161                       } else if (gotPremove) {
4162                         gotPremove = 0;
4163                         ClearPremoveHighlights();
4164                         if (appData.debugMode)
4165                           fprintf(debugFP, "Sending premove:\n");
4166                           UserMoveEvent(premoveFromX, premoveFromY,
4167                                         premoveToX, premoveToY,
4168                                         premovePromoChar);
4169                       }
4170                     }
4171
4172                     /* Usually suppress following prompt */
4173                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4174                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4175                         if (looking_at(buf, &i, "*% ")) {
4176                             savingComment = FALSE;
4177                             suppressKibitz = 0;
4178                         }
4179                     }
4180                     next_out = i;
4181                 } else if (started == STARTED_HOLDINGS) {
4182                     int gamenum;
4183                     char new_piece[MSG_SIZ];
4184                     started = STARTED_NONE;
4185                     parse[parse_pos] = NULLCHAR;
4186                     if (appData.debugMode)
4187                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4188                                                         parse, currentMove);
4189                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4190                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4191                         if (gameInfo.variant == VariantNormal) {
4192                           /* [HGM] We seem to switch variant during a game!
4193                            * Presumably no holdings were displayed, so we have
4194                            * to move the position two files to the right to
4195                            * create room for them!
4196                            */
4197                           VariantClass newVariant;
4198                           switch(gameInfo.boardWidth) { // base guess on board width
4199                                 case 9:  newVariant = VariantShogi; break;
4200                                 case 10: newVariant = VariantGreat; break;
4201                                 default: newVariant = VariantCrazyhouse; break;
4202                           }
4203                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4204                           /* Get a move list just to see the header, which
4205                              will tell us whether this is really bug or zh */
4206                           if (ics_getting_history == H_FALSE) {
4207                             ics_getting_history = H_REQUESTED;
4208                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4209                             SendToICS(str);
4210                           }
4211                         }
4212                         new_piece[0] = NULLCHAR;
4213                         sscanf(parse, "game %d white [%s black [%s <- %s",
4214                                &gamenum, white_holding, black_holding,
4215                                new_piece);
4216                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4217                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4218                         /* [HGM] copy holdings to board holdings area */
4219                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4220                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4221                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4222 #if ZIPPY
4223                         if (appData.zippyPlay && first.initDone) {
4224                             ZippyHoldings(white_holding, black_holding,
4225                                           new_piece);
4226                         }
4227 #endif /*ZIPPY*/
4228                         if (tinyLayout || smallLayout) {
4229                             char wh[16], bh[16];
4230                             PackHolding(wh, white_holding);
4231                             PackHolding(bh, black_holding);
4232                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4233                                     gameInfo.white, gameInfo.black);
4234                         } else {
4235                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4236                                     gameInfo.white, white_holding, _("vs."),
4237                                     gameInfo.black, black_holding);
4238                         }
4239                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4240                         DrawPosition(FALSE, boards[currentMove]);
4241                         DisplayTitle(str);
4242                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4243                         sscanf(parse, "game %d white [%s black [%s <- %s",
4244                                &gamenum, white_holding, black_holding,
4245                                new_piece);
4246                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4247                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4248                         /* [HGM] copy holdings to partner-board holdings area */
4249                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4250                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4251                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4252                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4253                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4254                       }
4255                     }
4256                     /* Suppress following prompt */
4257                     if (looking_at(buf, &i, "*% ")) {
4258                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4259                         savingComment = FALSE;
4260                         suppressKibitz = 0;
4261                     }
4262                     next_out = i;
4263                 }
4264                 continue;
4265             }
4266
4267             i++;                /* skip unparsed character and loop back */
4268         }
4269
4270         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4271 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4272 //          SendToPlayer(&buf[next_out], i - next_out);
4273             started != STARTED_HOLDINGS && leftover_start > next_out) {
4274             SendToPlayer(&buf[next_out], leftover_start - next_out);
4275             next_out = i;
4276         }
4277
4278         leftover_len = buf_len - leftover_start;
4279         /* if buffer ends with something we couldn't parse,
4280            reparse it after appending the next read */
4281
4282     } else if (count == 0) {
4283         RemoveInputSource(isr);
4284         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4285     } else {
4286         DisplayFatalError(_("Error reading from ICS"), error, 1);
4287     }
4288 }
4289
4290
4291 /* Board style 12 looks like this:
4292
4293    <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
4294
4295  * The "<12> " is stripped before it gets to this routine.  The two
4296  * trailing 0's (flip state and clock ticking) are later addition, and
4297  * some chess servers may not have them, or may have only the first.
4298  * Additional trailing fields may be added in the future.
4299  */
4300
4301 #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"
4302
4303 #define RELATION_OBSERVING_PLAYED    0
4304 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4305 #define RELATION_PLAYING_MYMOVE      1
4306 #define RELATION_PLAYING_NOTMYMOVE  -1
4307 #define RELATION_EXAMINING           2
4308 #define RELATION_ISOLATED_BOARD     -3
4309 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4310
4311 void
4312 ParseBoard12 (char *string)
4313 {
4314 #if ZIPPY
4315     int i, takeback;
4316     char *bookHit = NULL; // [HGM] book
4317 #endif
4318     GameMode newGameMode;
4319     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4320     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4321     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4322     char to_play, board_chars[200];
4323     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4324     char black[32], white[32];
4325     Board board;
4326     int prevMove = currentMove;
4327     int ticking = 2;
4328     ChessMove moveType;
4329     int fromX, fromY, toX, toY;
4330     char promoChar;
4331     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4332     Boolean weird = FALSE, reqFlag = FALSE;
4333
4334     fromX = fromY = toX = toY = -1;
4335
4336     newGame = FALSE;
4337
4338     if (appData.debugMode)
4339       fprintf(debugFP, "Parsing board: %s\n", string);
4340
4341     move_str[0] = NULLCHAR;
4342     elapsed_time[0] = NULLCHAR;
4343     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4344         int  i = 0, j;
4345         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4346             if(string[i] == ' ') { ranks++; files = 0; }
4347             else files++;
4348             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4349             i++;
4350         }
4351         for(j = 0; j <i; j++) board_chars[j] = string[j];
4352         board_chars[i] = '\0';
4353         string += i + 1;
4354     }
4355     n = sscanf(string, PATTERN, &to_play, &double_push,
4356                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4357                &gamenum, white, black, &relation, &basetime, &increment,
4358                &white_stren, &black_stren, &white_time, &black_time,
4359                &moveNum, str, elapsed_time, move_str, &ics_flip,
4360                &ticking);
4361
4362     if (n < 21) {
4363         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4364         DisplayError(str, 0);
4365         return;
4366     }
4367
4368     /* Convert the move number to internal form */
4369     moveNum = (moveNum - 1) * 2;
4370     if (to_play == 'B') moveNum++;
4371     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4372       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4373                         0, 1);
4374       return;
4375     }
4376
4377     switch (relation) {
4378       case RELATION_OBSERVING_PLAYED:
4379       case RELATION_OBSERVING_STATIC:
4380         if (gamenum == -1) {
4381             /* Old ICC buglet */
4382             relation = RELATION_OBSERVING_STATIC;
4383         }
4384         newGameMode = IcsObserving;
4385         break;
4386       case RELATION_PLAYING_MYMOVE:
4387       case RELATION_PLAYING_NOTMYMOVE:
4388         newGameMode =
4389           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4390             IcsPlayingWhite : IcsPlayingBlack;
4391         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4392         break;
4393       case RELATION_EXAMINING:
4394         newGameMode = IcsExamining;
4395         break;
4396       case RELATION_ISOLATED_BOARD:
4397       default:
4398         /* Just display this board.  If user was doing something else,
4399            we will forget about it until the next board comes. */
4400         newGameMode = IcsIdle;
4401         break;
4402       case RELATION_STARTING_POSITION:
4403         newGameMode = gameMode;
4404         break;
4405     }
4406
4407     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4408         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4409          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4410       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4411       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4412       static int lastBgGame = -1;
4413       char *toSqr;
4414       for (k = 0; k < ranks; k++) {
4415         for (j = 0; j < files; j++)
4416           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4417         if(gameInfo.holdingsWidth > 1) {
4418              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4419              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4420         }
4421       }
4422       CopyBoard(partnerBoard, board);
4423       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4424         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4425         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4426       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4427       if(toSqr = strchr(str, '-')) {
4428         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4429         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4430       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4431       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4432       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4433       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4434       if(twoBoards) {
4435           DisplayWhiteClock(white_time*fac, to_play == 'W');
4436           DisplayBlackClock(black_time*fac, to_play != 'W');
4437           activePartner = to_play;
4438           if(gamenum != lastBgGame) {
4439               char buf[MSG_SIZ];
4440               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4441               DisplayTitle(buf);
4442           }
4443           lastBgGame = gamenum;
4444           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4445                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4446       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4447                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4448       if(!twoBoards) DisplayMessage(partnerStatus, "");
4449         partnerBoardValid = TRUE;
4450       return;
4451     }
4452
4453     if(appData.dualBoard && appData.bgObserve) {
4454         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4455             SendToICS(ics_prefix), SendToICS("pobserve\n");
4456         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4457             char buf[MSG_SIZ];
4458             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4459             SendToICS(buf);
4460         }
4461     }
4462
4463     /* Modify behavior for initial board display on move listing
4464        of wild games.
4465        */
4466     switch (ics_getting_history) {
4467       case H_FALSE:
4468       case H_REQUESTED:
4469         break;
4470       case H_GOT_REQ_HEADER:
4471       case H_GOT_UNREQ_HEADER:
4472         /* This is the initial position of the current game */
4473         gamenum = ics_gamenum;
4474         moveNum = 0;            /* old ICS bug workaround */
4475         if (to_play == 'B') {
4476           startedFromSetupPosition = TRUE;
4477           blackPlaysFirst = TRUE;
4478           moveNum = 1;
4479           if (forwardMostMove == 0) forwardMostMove = 1;
4480           if (backwardMostMove == 0) backwardMostMove = 1;
4481           if (currentMove == 0) currentMove = 1;
4482         }
4483         newGameMode = gameMode;
4484         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4485         break;
4486       case H_GOT_UNWANTED_HEADER:
4487         /* This is an initial board that we don't want */
4488         return;
4489       case H_GETTING_MOVES:
4490         /* Should not happen */
4491         DisplayError(_("Error gathering move list: extra board"), 0);
4492         ics_getting_history = H_FALSE;
4493         return;
4494     }
4495
4496    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4497                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4498                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4499      /* [HGM] We seem to have switched variant unexpectedly
4500       * Try to guess new variant from board size
4501       */
4502           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4503           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4504           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4505           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4506           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4507           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4508           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4509           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4510           /* Get a move list just to see the header, which
4511              will tell us whether this is really bug or zh */
4512           if (ics_getting_history == H_FALSE) {
4513             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4514             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4515             SendToICS(str);
4516           }
4517     }
4518
4519     /* Take action if this is the first board of a new game, or of a
4520        different game than is currently being displayed.  */
4521     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4522         relation == RELATION_ISOLATED_BOARD) {
4523
4524         /* Forget the old game and get the history (if any) of the new one */
4525         if (gameMode != BeginningOfGame) {
4526           Reset(TRUE, TRUE);
4527         }
4528         newGame = TRUE;
4529         if (appData.autoRaiseBoard) BoardToTop();
4530         prevMove = -3;
4531         if (gamenum == -1) {
4532             newGameMode = IcsIdle;
4533         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4534                    appData.getMoveList && !reqFlag) {
4535             /* Need to get game history */
4536             ics_getting_history = H_REQUESTED;
4537             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4538             SendToICS(str);
4539         }
4540
4541         /* Initially flip the board to have black on the bottom if playing
4542            black or if the ICS flip flag is set, but let the user change
4543            it with the Flip View button. */
4544         flipView = appData.autoFlipView ?
4545           (newGameMode == IcsPlayingBlack) || ics_flip :
4546           appData.flipView;
4547
4548         /* Done with values from previous mode; copy in new ones */
4549         gameMode = newGameMode;
4550         ModeHighlight();
4551         ics_gamenum = gamenum;
4552         if (gamenum == gs_gamenum) {
4553             int klen = strlen(gs_kind);
4554             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4555             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4556             gameInfo.event = StrSave(str);
4557         } else {
4558             gameInfo.event = StrSave("ICS game");
4559         }
4560         gameInfo.site = StrSave(appData.icsHost);
4561         gameInfo.date = PGNDate();
4562         gameInfo.round = StrSave("-");
4563         gameInfo.white = StrSave(white);
4564         gameInfo.black = StrSave(black);
4565         timeControl = basetime * 60 * 1000;
4566         timeControl_2 = 0;
4567         timeIncrement = increment * 1000;
4568         movesPerSession = 0;
4569         gameInfo.timeControl = TimeControlTagValue();
4570         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4571   if (appData.debugMode) {
4572     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4573     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4574     setbuf(debugFP, NULL);
4575   }
4576
4577         gameInfo.outOfBook = NULL;
4578
4579         /* Do we have the ratings? */
4580         if (strcmp(player1Name, white) == 0 &&
4581             strcmp(player2Name, black) == 0) {
4582             if (appData.debugMode)
4583               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4584                       player1Rating, player2Rating);
4585             gameInfo.whiteRating = player1Rating;
4586             gameInfo.blackRating = player2Rating;
4587         } else if (strcmp(player2Name, white) == 0 &&
4588                    strcmp(player1Name, black) == 0) {
4589             if (appData.debugMode)
4590               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4591                       player2Rating, player1Rating);
4592             gameInfo.whiteRating = player2Rating;
4593             gameInfo.blackRating = player1Rating;
4594         }
4595         player1Name[0] = player2Name[0] = NULLCHAR;
4596
4597         /* Silence shouts if requested */
4598         if (appData.quietPlay &&
4599             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4600             SendToICS(ics_prefix);
4601             SendToICS("set shout 0\n");
4602         }
4603     }
4604
4605     /* Deal with midgame name changes */
4606     if (!newGame) {
4607         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4608             if (gameInfo.white) free(gameInfo.white);
4609             gameInfo.white = StrSave(white);
4610         }
4611         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4612             if (gameInfo.black) free(gameInfo.black);
4613             gameInfo.black = StrSave(black);
4614         }
4615     }
4616
4617     /* Throw away game result if anything actually changes in examine mode */
4618     if (gameMode == IcsExamining && !newGame) {
4619         gameInfo.result = GameUnfinished;
4620         if (gameInfo.resultDetails != NULL) {
4621             free(gameInfo.resultDetails);
4622             gameInfo.resultDetails = NULL;
4623         }
4624     }
4625
4626     /* In pausing && IcsExamining mode, we ignore boards coming
4627        in if they are in a different variation than we are. */
4628     if (pauseExamInvalid) return;
4629     if (pausing && gameMode == IcsExamining) {
4630         if (moveNum <= pauseExamForwardMostMove) {
4631             pauseExamInvalid = TRUE;
4632             forwardMostMove = pauseExamForwardMostMove;
4633             return;
4634         }
4635     }
4636
4637   if (appData.debugMode) {
4638     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4639   }
4640     /* Parse the board */
4641     for (k = 0; k < ranks; k++) {
4642       for (j = 0; j < files; j++)
4643         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4644       if(gameInfo.holdingsWidth > 1) {
4645            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4646            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4647       }
4648     }
4649     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4650       board[5][BOARD_RGHT+1] = WhiteAngel;
4651       board[6][BOARD_RGHT+1] = WhiteMarshall;
4652       board[1][0] = BlackMarshall;
4653       board[2][0] = BlackAngel;
4654       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4655     }
4656     CopyBoard(boards[moveNum], board);
4657     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4658     if (moveNum == 0) {
4659         startedFromSetupPosition =
4660           !CompareBoards(board, initialPosition);
4661         if(startedFromSetupPosition)
4662             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4663     }
4664
4665     /* [HGM] Set castling rights. Take the outermost Rooks,
4666        to make it also work for FRC opening positions. Note that board12
4667        is really defective for later FRC positions, as it has no way to
4668        indicate which Rook can castle if they are on the same side of King.
4669        For the initial position we grant rights to the outermost Rooks,
4670        and remember thos rights, and we then copy them on positions
4671        later in an FRC game. This means WB might not recognize castlings with
4672        Rooks that have moved back to their original position as illegal,
4673        but in ICS mode that is not its job anyway.
4674     */
4675     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4676     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4677
4678         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4679             if(board[0][i] == WhiteRook) j = i;
4680         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4681         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4682             if(board[0][i] == WhiteRook) j = i;
4683         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4684         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4685             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4686         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4687         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4688             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4689         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4690
4691         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4692         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4693         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4694             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4695         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4696             if(board[BOARD_HEIGHT-1][k] == bKing)
4697                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4698         if(gameInfo.variant == VariantTwoKings) {
4699             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4700             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4701             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4702         }
4703     } else { int r;
4704         r = boards[moveNum][CASTLING][0] = initialRights[0];
4705         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4706         r = boards[moveNum][CASTLING][1] = initialRights[1];
4707         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4708         r = boards[moveNum][CASTLING][3] = initialRights[3];
4709         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4710         r = boards[moveNum][CASTLING][4] = initialRights[4];
4711         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4712         /* wildcastle kludge: always assume King has rights */
4713         r = boards[moveNum][CASTLING][2] = initialRights[2];
4714         r = boards[moveNum][CASTLING][5] = initialRights[5];
4715     }
4716     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4717     boards[moveNum][EP_STATUS] = EP_NONE;
4718     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4719     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4720     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4721
4722
4723     if (ics_getting_history == H_GOT_REQ_HEADER ||
4724         ics_getting_history == H_GOT_UNREQ_HEADER) {
4725         /* This was an initial position from a move list, not
4726            the current position */
4727         return;
4728     }
4729
4730     /* Update currentMove and known move number limits */
4731     newMove = newGame || moveNum > forwardMostMove;
4732
4733     if (newGame) {
4734         forwardMostMove = backwardMostMove = currentMove = moveNum;
4735         if (gameMode == IcsExamining && moveNum == 0) {
4736           /* Workaround for ICS limitation: we are not told the wild
4737              type when starting to examine a game.  But if we ask for
4738              the move list, the move list header will tell us */
4739             ics_getting_history = H_REQUESTED;
4740             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4741             SendToICS(str);
4742         }
4743     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4744                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4745 #if ZIPPY
4746         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4747         /* [HGM] applied this also to an engine that is silently watching        */
4748         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4749             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4750             gameInfo.variant == currentlyInitializedVariant) {
4751           takeback = forwardMostMove - moveNum;
4752           for (i = 0; i < takeback; i++) {
4753             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4754             SendToProgram("undo\n", &first);
4755           }
4756         }
4757 #endif
4758
4759         forwardMostMove = moveNum;
4760         if (!pausing || currentMove > forwardMostMove)
4761           currentMove = forwardMostMove;
4762     } else {
4763         /* New part of history that is not contiguous with old part */
4764         if (pausing && gameMode == IcsExamining) {
4765             pauseExamInvalid = TRUE;
4766             forwardMostMove = pauseExamForwardMostMove;
4767             return;
4768         }
4769         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4770 #if ZIPPY
4771             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4772                 // [HGM] when we will receive the move list we now request, it will be
4773                 // fed to the engine from the first move on. So if the engine is not
4774                 // in the initial position now, bring it there.
4775                 InitChessProgram(&first, 0);
4776             }
4777 #endif
4778             ics_getting_history = H_REQUESTED;
4779             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4780             SendToICS(str);
4781         }
4782         forwardMostMove = backwardMostMove = currentMove = moveNum;
4783     }
4784
4785     /* Update the clocks */
4786     if (strchr(elapsed_time, '.')) {
4787       /* Time is in ms */
4788       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4789       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4790     } else {
4791       /* Time is in seconds */
4792       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4793       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4794     }
4795
4796
4797 #if ZIPPY
4798     if (appData.zippyPlay && newGame &&
4799         gameMode != IcsObserving && gameMode != IcsIdle &&
4800         gameMode != IcsExamining)
4801       ZippyFirstBoard(moveNum, basetime, increment);
4802 #endif
4803
4804     /* Put the move on the move list, first converting
4805        to canonical algebraic form. */
4806     if (moveNum > 0) {
4807   if (appData.debugMode) {
4808     int f = forwardMostMove;
4809     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4810             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4811             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4812     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4813     fprintf(debugFP, "moveNum = %d\n", moveNum);
4814     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4815     setbuf(debugFP, NULL);
4816   }
4817         if (moveNum <= backwardMostMove) {
4818             /* We don't know what the board looked like before
4819                this move.  Punt. */
4820           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4821             strcat(parseList[moveNum - 1], " ");
4822             strcat(parseList[moveNum - 1], elapsed_time);
4823             moveList[moveNum - 1][0] = NULLCHAR;
4824         } else if (strcmp(move_str, "none") == 0) {
4825             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4826             /* Again, we don't know what the board looked like;
4827                this is really the start of the game. */
4828             parseList[moveNum - 1][0] = NULLCHAR;
4829             moveList[moveNum - 1][0] = NULLCHAR;
4830             backwardMostMove = moveNum;
4831             startedFromSetupPosition = TRUE;
4832             fromX = fromY = toX = toY = -1;
4833         } else {
4834           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4835           //                 So we parse the long-algebraic move string in stead of the SAN move
4836           int valid; char buf[MSG_SIZ], *prom;
4837
4838           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4839                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4840           // str looks something like "Q/a1-a2"; kill the slash
4841           if(str[1] == '/')
4842             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4843           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4844           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4845                 strcat(buf, prom); // long move lacks promo specification!
4846           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4847                 if(appData.debugMode)
4848                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4849                 safeStrCpy(move_str, buf, MSG_SIZ);
4850           }
4851           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4852                                 &fromX, &fromY, &toX, &toY, &promoChar)
4853                || ParseOneMove(buf, moveNum - 1, &moveType,
4854                                 &fromX, &fromY, &toX, &toY, &promoChar);
4855           // end of long SAN patch
4856           if (valid) {
4857             (void) CoordsToAlgebraic(boards[moveNum - 1],
4858                                      PosFlags(moveNum - 1),
4859                                      fromY, fromX, toY, toX, promoChar,
4860                                      parseList[moveNum-1]);
4861             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4862               case MT_NONE:
4863               case MT_STALEMATE:
4864               default:
4865                 break;
4866               case MT_CHECK:
4867                 if(!IS_SHOGI(gameInfo.variant))
4868                     strcat(parseList[moveNum - 1], "+");
4869                 break;
4870               case MT_CHECKMATE:
4871               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4872                 strcat(parseList[moveNum - 1], "#");
4873                 break;
4874             }
4875             strcat(parseList[moveNum - 1], " ");
4876             strcat(parseList[moveNum - 1], elapsed_time);
4877             /* currentMoveString is set as a side-effect of ParseOneMove */
4878             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4879             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4880             strcat(moveList[moveNum - 1], "\n");
4881
4882             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4883                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4884               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4885                 ChessSquare old, new = boards[moveNum][k][j];
4886                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4887                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4888                   if(old == new) continue;
4889                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4890                   else if(new == WhiteWazir || new == BlackWazir) {
4891                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4892                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4893                       else boards[moveNum][k][j] = old; // preserve type of Gold
4894                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4895                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4896               }
4897           } else {
4898             /* Move from ICS was illegal!?  Punt. */
4899             if (appData.debugMode) {
4900               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4901               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4902             }
4903             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4904             strcat(parseList[moveNum - 1], " ");
4905             strcat(parseList[moveNum - 1], elapsed_time);
4906             moveList[moveNum - 1][0] = NULLCHAR;
4907             fromX = fromY = toX = toY = -1;
4908           }
4909         }
4910   if (appData.debugMode) {
4911     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4912     setbuf(debugFP, NULL);
4913   }
4914
4915 #if ZIPPY
4916         /* Send move to chess program (BEFORE animating it). */
4917         if (appData.zippyPlay && !newGame && newMove &&
4918            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4919
4920             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4921                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4922                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4923                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4924                             move_str);
4925                     DisplayError(str, 0);
4926                 } else {
4927                     if (first.sendTime) {
4928                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4929                     }
4930                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4931                     if (firstMove && !bookHit) {
4932                         firstMove = FALSE;
4933                         if (first.useColors) {
4934                           SendToProgram(gameMode == IcsPlayingWhite ?
4935                                         "white\ngo\n" :
4936                                         "black\ngo\n", &first);
4937                         } else {
4938                           SendToProgram("go\n", &first);
4939                         }
4940                         first.maybeThinking = TRUE;
4941                     }
4942                 }
4943             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4944               if (moveList[moveNum - 1][0] == NULLCHAR) {
4945                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4946                 DisplayError(str, 0);
4947               } else {
4948                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4949                 SendMoveToProgram(moveNum - 1, &first);
4950               }
4951             }
4952         }
4953 #endif
4954     }
4955
4956     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4957         /* If move comes from a remote source, animate it.  If it
4958            isn't remote, it will have already been animated. */
4959         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4960             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4961         }
4962         if (!pausing && appData.highlightLastMove) {
4963             SetHighlights(fromX, fromY, toX, toY);
4964         }
4965     }
4966
4967     /* Start the clocks */
4968     whiteFlag = blackFlag = FALSE;
4969     appData.clockMode = !(basetime == 0 && increment == 0);
4970     if (ticking == 0) {
4971       ics_clock_paused = TRUE;
4972       StopClocks();
4973     } else if (ticking == 1) {
4974       ics_clock_paused = FALSE;
4975     }
4976     if (gameMode == IcsIdle ||
4977         relation == RELATION_OBSERVING_STATIC ||
4978         relation == RELATION_EXAMINING ||
4979         ics_clock_paused)
4980       DisplayBothClocks();
4981     else
4982       StartClocks();
4983
4984     /* Display opponents and material strengths */
4985     if (gameInfo.variant != VariantBughouse &&
4986         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4987         if (tinyLayout || smallLayout) {
4988             if(gameInfo.variant == VariantNormal)
4989               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4990                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4991                     basetime, increment);
4992             else
4993               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4994                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4995                     basetime, increment, (int) gameInfo.variant);
4996         } else {
4997             if(gameInfo.variant == VariantNormal)
4998               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4999                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5000                     basetime, increment);
5001             else
5002               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5003                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5004                     basetime, increment, VariantName(gameInfo.variant));
5005         }
5006         DisplayTitle(str);
5007   if (appData.debugMode) {
5008     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5009   }
5010     }
5011
5012
5013     /* Display the board */
5014     if (!pausing && !appData.noGUI) {
5015
5016       if (appData.premove)
5017           if (!gotPremove ||
5018              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5019              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5020               ClearPremoveHighlights();
5021
5022       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5023         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5024       DrawPosition(j, boards[currentMove]);
5025
5026       DisplayMove(moveNum - 1);
5027       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5028             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5029               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5030         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5031       }
5032     }
5033
5034     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5035 #if ZIPPY
5036     if(bookHit) { // [HGM] book: simulate book reply
5037         static char bookMove[MSG_SIZ]; // a bit generous?
5038
5039         programStats.nodes = programStats.depth = programStats.time =
5040         programStats.score = programStats.got_only_move = 0;
5041         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5042
5043         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5044         strcat(bookMove, bookHit);
5045         HandleMachineMove(bookMove, &first);
5046     }
5047 #endif
5048 }
5049
5050 void
5051 GetMoveListEvent ()
5052 {
5053     char buf[MSG_SIZ];
5054     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5055         ics_getting_history = H_REQUESTED;
5056         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5057         SendToICS(buf);
5058     }
5059 }
5060
5061 void
5062 SendToBoth (char *msg)
5063 {   // to make it easy to keep two engines in step in dual analysis
5064     SendToProgram(msg, &first);
5065     if(second.analyzing) SendToProgram(msg, &second);
5066 }
5067
5068 void
5069 AnalysisPeriodicEvent (int force)
5070 {
5071     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5072          && !force) || !appData.periodicUpdates)
5073       return;
5074
5075     /* Send . command to Crafty to collect stats */
5076     SendToBoth(".\n");
5077
5078     /* Don't send another until we get a response (this makes
5079        us stop sending to old Crafty's which don't understand
5080        the "." command (sending illegal cmds resets node count & time,
5081        which looks bad)) */
5082     programStats.ok_to_send = 0;
5083 }
5084
5085 void
5086 ics_update_width (int new_width)
5087 {
5088         ics_printf("set width %d\n", new_width);
5089 }
5090
5091 void
5092 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5093 {
5094     char buf[MSG_SIZ];
5095
5096     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5097         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5098             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5099             SendToProgram(buf, cps);
5100             return;
5101         }
5102         // null move in variant where engine does not understand it (for analysis purposes)
5103         SendBoard(cps, moveNum + 1); // send position after move in stead.
5104         return;
5105     }
5106     if (cps->useUsermove) {
5107       SendToProgram("usermove ", cps);
5108     }
5109     if (cps->useSAN) {
5110       char *space;
5111       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5112         int len = space - parseList[moveNum];
5113         memcpy(buf, parseList[moveNum], len);
5114         buf[len++] = '\n';
5115         buf[len] = NULLCHAR;
5116       } else {
5117         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5118       }
5119       SendToProgram(buf, cps);
5120     } else {
5121       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5122         AlphaRank(moveList[moveNum], 4);
5123         SendToProgram(moveList[moveNum], cps);
5124         AlphaRank(moveList[moveNum], 4); // and back
5125       } else
5126       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5127        * the engine. It would be nice to have a better way to identify castle
5128        * moves here. */
5129       if(appData.fischerCastling && cps->useOOCastle) {
5130         int fromX = moveList[moveNum][0] - AAA;
5131         int fromY = moveList[moveNum][1] - ONE;
5132         int toX = moveList[moveNum][2] - AAA;
5133         int toY = moveList[moveNum][3] - ONE;
5134         if((boards[moveNum][fromY][fromX] == WhiteKing
5135             && boards[moveNum][toY][toX] == WhiteRook)
5136            || (boards[moveNum][fromY][fromX] == BlackKing
5137                && boards[moveNum][toY][toX] == BlackRook)) {
5138           if(toX > fromX) SendToProgram("O-O\n", cps);
5139           else SendToProgram("O-O-O\n", cps);
5140         }
5141         else SendToProgram(moveList[moveNum], cps);
5142       } else
5143       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5144         char *m = moveList[moveNum];
5145         if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
5146           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5147                                                m[2], m[3] - '0',
5148                                                m[5], m[6] - '0',
5149                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5150         else
5151           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5152                                                m[5], m[6] - '0',
5153                                                m[5], m[6] - '0',
5154                                                m[2], m[3] - '0');
5155           SendToProgram(buf, cps);
5156       } else
5157       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5158         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5159           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5160           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5161                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5162         } else
5163           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5164                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5165         SendToProgram(buf, cps);
5166       }
5167       else SendToProgram(moveList[moveNum], cps);
5168       /* End of additions by Tord */
5169     }
5170
5171     /* [HGM] setting up the opening has brought engine in force mode! */
5172     /*       Send 'go' if we are in a mode where machine should play. */
5173     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5174         (gameMode == TwoMachinesPlay   ||
5175 #if ZIPPY
5176          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5177 #endif
5178          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5179         SendToProgram("go\n", cps);
5180   if (appData.debugMode) {
5181     fprintf(debugFP, "(extra)\n");
5182   }
5183     }
5184     setboardSpoiledMachineBlack = 0;
5185 }
5186
5187 void
5188 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5189 {
5190     char user_move[MSG_SIZ];
5191     char suffix[4];
5192
5193     if(gameInfo.variant == VariantSChess && promoChar) {
5194         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5195         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5196     } else suffix[0] = NULLCHAR;
5197
5198     switch (moveType) {
5199       default:
5200         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5201                 (int)moveType, fromX, fromY, toX, toY);
5202         DisplayError(user_move + strlen("say "), 0);
5203         break;
5204       case WhiteKingSideCastle:
5205       case BlackKingSideCastle:
5206       case WhiteQueenSideCastleWild:
5207       case BlackQueenSideCastleWild:
5208       /* PUSH Fabien */
5209       case WhiteHSideCastleFR:
5210       case BlackHSideCastleFR:
5211       /* POP Fabien */
5212         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5213         break;
5214       case WhiteQueenSideCastle:
5215       case BlackQueenSideCastle:
5216       case WhiteKingSideCastleWild:
5217       case BlackKingSideCastleWild:
5218       /* PUSH Fabien */
5219       case WhiteASideCastleFR:
5220       case BlackASideCastleFR:
5221       /* POP Fabien */
5222         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5223         break;
5224       case WhiteNonPromotion:
5225       case BlackNonPromotion:
5226         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5227         break;
5228       case WhitePromotion:
5229       case BlackPromotion:
5230         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5231            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5232           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5233                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5234                 PieceToChar(WhiteFerz));
5235         else if(gameInfo.variant == VariantGreat)
5236           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5237                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5238                 PieceToChar(WhiteMan));
5239         else
5240           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5241                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5242                 promoChar);
5243         break;
5244       case WhiteDrop:
5245       case BlackDrop:
5246       drop:
5247         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5248                  ToUpper(PieceToChar((ChessSquare) fromX)),
5249                  AAA + toX, ONE + toY);
5250         break;
5251       case IllegalMove:  /* could be a variant we don't quite understand */
5252         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5253       case NormalMove:
5254       case WhiteCapturesEnPassant:
5255       case BlackCapturesEnPassant:
5256         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5257                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5258         break;
5259     }
5260     SendToICS(user_move);
5261     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5262         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5263 }
5264
5265 void
5266 UploadGameEvent ()
5267 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5268     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5269     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5270     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5271       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5272       return;
5273     }
5274     if(gameMode != IcsExamining) { // is this ever not the case?
5275         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5276
5277         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5278           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5279         } else { // on FICS we must first go to general examine mode
5280           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5281         }
5282         if(gameInfo.variant != VariantNormal) {
5283             // try figure out wild number, as xboard names are not always valid on ICS
5284             for(i=1; i<=36; i++) {
5285               snprintf(buf, MSG_SIZ, "wild/%d", i);
5286                 if(StringToVariant(buf) == gameInfo.variant) break;
5287             }
5288             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5289             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5290             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5291         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5292         SendToICS(ics_prefix);
5293         SendToICS(buf);
5294         if(startedFromSetupPosition || backwardMostMove != 0) {
5295           fen = PositionToFEN(backwardMostMove, NULL, 1);
5296           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5297             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5298             SendToICS(buf);
5299           } else { // FICS: everything has to set by separate bsetup commands
5300             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5301             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5302             SendToICS(buf);
5303             if(!WhiteOnMove(backwardMostMove)) {
5304                 SendToICS("bsetup tomove black\n");
5305             }
5306             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5307             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5308             SendToICS(buf);
5309             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5310             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5311             SendToICS(buf);
5312             i = boards[backwardMostMove][EP_STATUS];
5313             if(i >= 0) { // set e.p.
5314               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5315                 SendToICS(buf);
5316             }
5317             bsetup++;
5318           }
5319         }
5320       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5321             SendToICS("bsetup done\n"); // switch to normal examining.
5322     }
5323     for(i = backwardMostMove; i<last; i++) {
5324         char buf[20];
5325         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5326         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5327             int len = strlen(moveList[i]);
5328             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5329             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5330         }
5331         SendToICS(buf);
5332     }
5333     SendToICS(ics_prefix);
5334     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5335 }
5336
5337 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5338 int legNr = 1;
5339
5340 void
5341 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5342 {
5343     if (rf == DROP_RANK) {
5344       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5345       sprintf(move, "%c@%c%c\n",
5346                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5347     } else {
5348         if (promoChar == 'x' || promoChar == NULLCHAR) {
5349           sprintf(move, "%c%c%c%c\n",
5350                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5351           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5352         } else {
5353             sprintf(move, "%c%c%c%c%c\n",
5354                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5355         }
5356     }
5357 }
5358
5359 void
5360 ProcessICSInitScript (FILE *f)
5361 {
5362     char buf[MSG_SIZ];
5363
5364     while (fgets(buf, MSG_SIZ, f)) {
5365         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5366     }
5367
5368     fclose(f);
5369 }
5370
5371
5372 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5373 int dragging;
5374 static ClickType lastClickType;
5375
5376 int
5377 Partner (ChessSquare *p)
5378 { // change piece into promotion partner if one shogi-promotes to the other
5379   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5380   ChessSquare partner;
5381   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5382   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5383   *p = partner;
5384   return 1;
5385 }
5386
5387 void
5388 Sweep (int step)
5389 {
5390     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5391     static int toggleFlag;
5392     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5393     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5394     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5395     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5396     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5397     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5398     do {
5399         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5400         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5401         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5402         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5403         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5404         if(!step) step = -1;
5405     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5406             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5407             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5408             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5409     if(toX >= 0) {
5410         int victim = boards[currentMove][toY][toX];
5411         boards[currentMove][toY][toX] = promoSweep;
5412         DrawPosition(FALSE, boards[currentMove]);
5413         boards[currentMove][toY][toX] = victim;
5414     } else
5415     ChangeDragPiece(promoSweep);
5416 }
5417
5418 int
5419 PromoScroll (int x, int y)
5420 {
5421   int step = 0;
5422
5423   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5424   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5425   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5426   if(!step) return FALSE;
5427   lastX = x; lastY = y;
5428   if((promoSweep < BlackPawn) == flipView) step = -step;
5429   if(step > 0) selectFlag = 1;
5430   if(!selectFlag) Sweep(step);
5431   return FALSE;
5432 }
5433
5434 void
5435 NextPiece (int step)
5436 {
5437     ChessSquare piece = boards[currentMove][toY][toX];
5438     do {
5439         pieceSweep -= step;
5440         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5441         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5442         if(!step) step = -1;
5443     } while(PieceToChar(pieceSweep) == '.');
5444     boards[currentMove][toY][toX] = pieceSweep;
5445     DrawPosition(FALSE, boards[currentMove]);
5446     boards[currentMove][toY][toX] = piece;
5447 }
5448 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5449 void
5450 AlphaRank (char *move, int n)
5451 {
5452 //    char *p = move, c; int x, y;
5453
5454     if (appData.debugMode) {
5455         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5456     }
5457
5458     if(move[1]=='*' &&
5459        move[2]>='0' && move[2]<='9' &&
5460        move[3]>='a' && move[3]<='x'    ) {
5461         move[1] = '@';
5462         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5463         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5464     } else
5465     if(move[0]>='0' && move[0]<='9' &&
5466        move[1]>='a' && move[1]<='x' &&
5467        move[2]>='0' && move[2]<='9' &&
5468        move[3]>='a' && move[3]<='x'    ) {
5469         /* input move, Shogi -> normal */
5470         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5471         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5472         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5473         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5474     } else
5475     if(move[1]=='@' &&
5476        move[3]>='0' && move[3]<='9' &&
5477        move[2]>='a' && move[2]<='x'    ) {
5478         move[1] = '*';
5479         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5480         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5481     } else
5482     if(
5483        move[0]>='a' && move[0]<='x' &&
5484        move[3]>='0' && move[3]<='9' &&
5485        move[2]>='a' && move[2]<='x'    ) {
5486          /* output move, normal -> Shogi */
5487         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5488         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5489         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5490         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5491         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5492     }
5493     if (appData.debugMode) {
5494         fprintf(debugFP, "   out = '%s'\n", move);
5495     }
5496 }
5497
5498 char yy_textstr[8000];
5499
5500 /* Parser for moves from gnuchess, ICS, or user typein box */
5501 Boolean
5502 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5503 {
5504     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5505
5506     switch (*moveType) {
5507       case WhitePromotion:
5508       case BlackPromotion:
5509       case WhiteNonPromotion:
5510       case BlackNonPromotion:
5511       case NormalMove:
5512       case FirstLeg:
5513       case WhiteCapturesEnPassant:
5514       case BlackCapturesEnPassant:
5515       case WhiteKingSideCastle:
5516       case WhiteQueenSideCastle:
5517       case BlackKingSideCastle:
5518       case BlackQueenSideCastle:
5519       case WhiteKingSideCastleWild:
5520       case WhiteQueenSideCastleWild:
5521       case BlackKingSideCastleWild:
5522       case BlackQueenSideCastleWild:
5523       /* Code added by Tord: */
5524       case WhiteHSideCastleFR:
5525       case WhiteASideCastleFR:
5526       case BlackHSideCastleFR:
5527       case BlackASideCastleFR:
5528       /* End of code added by Tord */
5529       case IllegalMove:         /* bug or odd chess variant */
5530         if(currentMoveString[1] == '@') { // illegal drop
5531           *fromX = WhiteOnMove(moveNum) ?
5532             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5533             (int) CharToPiece(ToLower(currentMoveString[0]));
5534           goto drop;
5535         }
5536         *fromX = currentMoveString[0] - AAA;
5537         *fromY = currentMoveString[1] - ONE;
5538         *toX = currentMoveString[2] - AAA;
5539         *toY = currentMoveString[3] - ONE;
5540         *promoChar = currentMoveString[4];
5541         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5542             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5543     if (appData.debugMode) {
5544         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5545     }
5546             *fromX = *fromY = *toX = *toY = 0;
5547             return FALSE;
5548         }
5549         if (appData.testLegality) {
5550           return (*moveType != IllegalMove);
5551         } else {
5552           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5553                          // [HGM] lion: if this is a double move we are less critical
5554                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5555         }
5556
5557       case WhiteDrop:
5558       case BlackDrop:
5559         *fromX = *moveType == WhiteDrop ?
5560           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5561           (int) CharToPiece(ToLower(currentMoveString[0]));
5562       drop:
5563         *fromY = DROP_RANK;
5564         *toX = currentMoveString[2] - AAA;
5565         *toY = currentMoveString[3] - ONE;
5566         *promoChar = NULLCHAR;
5567         return TRUE;
5568
5569       case AmbiguousMove:
5570       case ImpossibleMove:
5571       case EndOfFile:
5572       case ElapsedTime:
5573       case Comment:
5574       case PGNTag:
5575       case NAG:
5576       case WhiteWins:
5577       case BlackWins:
5578       case GameIsDrawn:
5579       default:
5580     if (appData.debugMode) {
5581         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5582     }
5583         /* bug? */
5584         *fromX = *fromY = *toX = *toY = 0;
5585         *promoChar = NULLCHAR;
5586         return FALSE;
5587     }
5588 }
5589
5590 Boolean pushed = FALSE;
5591 char *lastParseAttempt;
5592
5593 void
5594 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5595 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5596   int fromX, fromY, toX, toY; char promoChar;
5597   ChessMove moveType;
5598   Boolean valid;
5599   int nr = 0;
5600
5601   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5602   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5603     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5604     pushed = TRUE;
5605   }
5606   endPV = forwardMostMove;
5607   do {
5608     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5609     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5610     lastParseAttempt = pv;
5611     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5612     if(!valid && nr == 0 &&
5613        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5614         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5615         // Hande case where played move is different from leading PV move
5616         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5617         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5618         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5619         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5620           endPV += 2; // if position different, keep this
5621           moveList[endPV-1][0] = fromX + AAA;
5622           moveList[endPV-1][1] = fromY + ONE;
5623           moveList[endPV-1][2] = toX + AAA;
5624           moveList[endPV-1][3] = toY + ONE;
5625           parseList[endPV-1][0] = NULLCHAR;
5626           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5627         }
5628       }
5629     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5630     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5631     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5632     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5633         valid++; // allow comments in PV
5634         continue;
5635     }
5636     nr++;
5637     if(endPV+1 > framePtr) break; // no space, truncate
5638     if(!valid) break;
5639     endPV++;
5640     CopyBoard(boards[endPV], boards[endPV-1]);
5641     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5642     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5643     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5644     CoordsToAlgebraic(boards[endPV - 1],
5645                              PosFlags(endPV - 1),
5646                              fromY, fromX, toY, toX, promoChar,
5647                              parseList[endPV - 1]);
5648   } while(valid);
5649   if(atEnd == 2) return; // used hidden, for PV conversion
5650   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5651   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5652   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5653                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5654   DrawPosition(TRUE, boards[currentMove]);
5655 }
5656
5657 int
5658 MultiPV (ChessProgramState *cps)
5659 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5660         int i;
5661         for(i=0; i<cps->nrOptions; i++)
5662             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5663                 return i;
5664         return -1;
5665 }
5666
5667 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5668
5669 Boolean
5670 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5671 {
5672         int startPV, multi, lineStart, origIndex = index;
5673         char *p, buf2[MSG_SIZ];
5674         ChessProgramState *cps = (pane ? &second : &first);
5675
5676         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5677         lastX = x; lastY = y;
5678         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5679         lineStart = startPV = index;
5680         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5681         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5682         index = startPV;
5683         do{ while(buf[index] && buf[index] != '\n') index++;
5684         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5685         buf[index] = 0;
5686         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5687                 int n = cps->option[multi].value;
5688                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5689                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5690                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5691                 cps->option[multi].value = n;
5692                 *start = *end = 0;
5693                 return FALSE;
5694         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5695                 ExcludeClick(origIndex - lineStart);
5696                 return FALSE;
5697         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5698                 Collapse(origIndex - lineStart);
5699                 return FALSE;
5700         }
5701         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5702         *start = startPV; *end = index-1;
5703         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5704         return TRUE;
5705 }
5706
5707 char *
5708 PvToSAN (char *pv)
5709 {
5710         static char buf[10*MSG_SIZ];
5711         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5712         *buf = NULLCHAR;
5713         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5714         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5715         for(i = forwardMostMove; i<endPV; i++){
5716             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5717             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5718             k += strlen(buf+k);
5719         }
5720         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5721         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5722         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5723         endPV = savedEnd;
5724         return buf;
5725 }
5726
5727 Boolean
5728 LoadPV (int x, int y)
5729 { // called on right mouse click to load PV
5730   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5731   lastX = x; lastY = y;
5732   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5733   extendGame = FALSE;
5734   return TRUE;
5735 }
5736
5737 void
5738 UnLoadPV ()
5739 {
5740   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5741   if(endPV < 0) return;
5742   if(appData.autoCopyPV) CopyFENToClipboard();
5743   endPV = -1;
5744   if(extendGame && currentMove > forwardMostMove) {
5745         Boolean saveAnimate = appData.animate;
5746         if(pushed) {
5747             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5748                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5749             } else storedGames--; // abandon shelved tail of original game
5750         }
5751         pushed = FALSE;
5752         forwardMostMove = currentMove;
5753         currentMove = oldFMM;
5754         appData.animate = FALSE;
5755         ToNrEvent(forwardMostMove);
5756         appData.animate = saveAnimate;
5757   }
5758   currentMove = forwardMostMove;
5759   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5760   ClearPremoveHighlights();
5761   DrawPosition(TRUE, boards[currentMove]);
5762 }
5763
5764 void
5765 MovePV (int x, int y, int h)
5766 { // step through PV based on mouse coordinates (called on mouse move)
5767   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5768
5769   // we must somehow check if right button is still down (might be released off board!)
5770   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5771   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5772   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5773   if(!step) return;
5774   lastX = x; lastY = y;
5775
5776   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5777   if(endPV < 0) return;
5778   if(y < margin) step = 1; else
5779   if(y > h - margin) step = -1;
5780   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5781   currentMove += step;
5782   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5783   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5784                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5785   DrawPosition(FALSE, boards[currentMove]);
5786 }
5787
5788
5789 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5790 // All positions will have equal probability, but the current method will not provide a unique
5791 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5792 #define DARK 1
5793 #define LITE 2
5794 #define ANY 3
5795
5796 int squaresLeft[4];
5797 int piecesLeft[(int)BlackPawn];
5798 int seed, nrOfShuffles;
5799
5800 void
5801 GetPositionNumber ()
5802 {       // sets global variable seed
5803         int i;
5804
5805         seed = appData.defaultFrcPosition;
5806         if(seed < 0) { // randomize based on time for negative FRC position numbers
5807                 for(i=0; i<50; i++) seed += random();
5808                 seed = random() ^ random() >> 8 ^ random() << 8;
5809                 if(seed<0) seed = -seed;
5810         }
5811 }
5812
5813 int
5814 put (Board board, int pieceType, int rank, int n, int shade)
5815 // put the piece on the (n-1)-th empty squares of the given shade
5816 {
5817         int i;
5818
5819         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5820                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5821                         board[rank][i] = (ChessSquare) pieceType;
5822                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5823                         squaresLeft[ANY]--;
5824                         piecesLeft[pieceType]--;
5825                         return i;
5826                 }
5827         }
5828         return -1;
5829 }
5830
5831
5832 void
5833 AddOnePiece (Board board, int pieceType, int rank, int shade)
5834 // calculate where the next piece goes, (any empty square), and put it there
5835 {
5836         int i;
5837
5838         i = seed % squaresLeft[shade];
5839         nrOfShuffles *= squaresLeft[shade];
5840         seed /= squaresLeft[shade];
5841         put(board, pieceType, rank, i, shade);
5842 }
5843
5844 void
5845 AddTwoPieces (Board board, int pieceType, int rank)
5846 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5847 {
5848         int i, n=squaresLeft[ANY], j=n-1, k;
5849
5850         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5851         i = seed % k;  // pick one
5852         nrOfShuffles *= k;
5853         seed /= k;
5854         while(i >= j) i -= j--;
5855         j = n - 1 - j; i += j;
5856         put(board, pieceType, rank, j, ANY);
5857         put(board, pieceType, rank, i, ANY);
5858 }
5859
5860 void
5861 SetUpShuffle (Board board, int number)
5862 {
5863         int i, p, first=1;
5864
5865         GetPositionNumber(); nrOfShuffles = 1;
5866
5867         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5868         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5869         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5870
5871         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5872
5873         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5874             p = (int) board[0][i];
5875             if(p < (int) BlackPawn) piecesLeft[p] ++;
5876             board[0][i] = EmptySquare;
5877         }
5878
5879         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5880             // shuffles restricted to allow normal castling put KRR first
5881             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5882                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5883             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5884                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5885             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5886                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5887             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5888                 put(board, WhiteRook, 0, 0, ANY);
5889             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5890         }
5891
5892         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5893             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5894             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5895                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5896                 while(piecesLeft[p] >= 2) {
5897                     AddOnePiece(board, p, 0, LITE);
5898                     AddOnePiece(board, p, 0, DARK);
5899                 }
5900                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5901             }
5902
5903         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5904             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5905             // but we leave King and Rooks for last, to possibly obey FRC restriction
5906             if(p == (int)WhiteRook) continue;
5907             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5908             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5909         }
5910
5911         // now everything is placed, except perhaps King (Unicorn) and Rooks
5912
5913         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5914             // Last King gets castling rights
5915             while(piecesLeft[(int)WhiteUnicorn]) {
5916                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5917                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5918             }
5919
5920             while(piecesLeft[(int)WhiteKing]) {
5921                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5922                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5923             }
5924
5925
5926         } else {
5927             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5928             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5929         }
5930
5931         // Only Rooks can be left; simply place them all
5932         while(piecesLeft[(int)WhiteRook]) {
5933                 i = put(board, WhiteRook, 0, 0, ANY);
5934                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5935                         if(first) {
5936                                 first=0;
5937                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5938                         }
5939                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5940                 }
5941         }
5942         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5943             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5944         }
5945
5946         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5947 }
5948
5949 int
5950 ptclen (const char *s, char *escapes)
5951 {
5952     int n = 0;
5953     if(!*escapes) return strlen(s);
5954     while(*s) n += (*s != ':' && !strchr(escapes, *s)), s++;
5955     return n;
5956 }
5957
5958 int
5959 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
5960 /* [HGM] moved here from winboard.c because of its general usefulness */
5961 /*       Basically a safe strcpy that uses the last character as King */
5962 {
5963     int result = FALSE; int NrPieces;
5964
5965     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
5966                     && NrPieces >= 12 && !(NrPieces&1)) {
5967         int i, j = 0; /* [HGM] Accept even length from 12 to 88 */
5968
5969         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5970         for( i=0; i<NrPieces/2-1; i++ ) {
5971             char *p;
5972             if(map[j] == ':' && *escapes) i = CHUPROMOTED WhitePawn, j++;
5973             table[i] = map[j++];
5974             if(p = strchr(escapes, map[j])) j++, table[i] += 64*(p - escapes + 1);
5975         }
5976         table[(int) WhiteKing]  = map[j++];
5977         for( i=0; i<NrPieces/2-1; i++ ) {
5978             char *p;
5979             if(map[j] == ':' && *escapes) i = CHUPROMOTED WhitePawn, j++;
5980             table[WHITE_TO_BLACK i] = map[j++];
5981             if(p = strchr(escapes, map[j])) j++, table[WHITE_TO_BLACK i] += 64*(p - escapes + 1);
5982         }
5983         table[(int) BlackKing]  = map[j++];
5984
5985         result = TRUE;
5986     }
5987
5988     return result;
5989 }
5990
5991 int
5992 SetCharTable (unsigned char *table, const char * map)
5993 {
5994     return SetCharTableEsc(table, map, "");
5995 }
5996
5997 void
5998 Prelude (Board board)
5999 {       // [HGM] superchess: random selection of exo-pieces
6000         int i, j, k; ChessSquare p;
6001         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6002
6003         GetPositionNumber(); // use FRC position number
6004
6005         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6006             SetCharTable(pieceToChar, appData.pieceToCharTable);
6007             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6008                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6009         }
6010
6011         j = seed%4;                 seed /= 4;
6012         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6013         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6014         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6015         j = seed%3 + (seed%3 >= j); seed /= 3;
6016         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6017         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6018         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6019         j = seed%3;                 seed /= 3;
6020         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6021         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6022         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6023         j = seed%2 + (seed%2 >= j); seed /= 2;
6024         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6025         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6026         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6027         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6028         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6029         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6030         put(board, exoPieces[0],    0, 0, ANY);
6031         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6032 }
6033
6034 void
6035 InitPosition (int redraw)
6036 {
6037     ChessSquare (* pieces)[BOARD_FILES];
6038     int i, j, pawnRow=1, pieceRows=1, overrule,
6039     oldx = gameInfo.boardWidth,
6040     oldy = gameInfo.boardHeight,
6041     oldh = gameInfo.holdingsWidth;
6042     static int oldv;
6043
6044     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6045
6046     /* [AS] Initialize pv info list [HGM] and game status */
6047     {
6048         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6049             pvInfoList[i].depth = 0;
6050             boards[i][EP_STATUS] = EP_NONE;
6051             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6052         }
6053
6054         initialRulePlies = 0; /* 50-move counter start */
6055
6056         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6057         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6058     }
6059
6060
6061     /* [HGM] logic here is completely changed. In stead of full positions */
6062     /* the initialized data only consist of the two backranks. The switch */
6063     /* selects which one we will use, which is than copied to the Board   */
6064     /* initialPosition, which for the rest is initialized by Pawns and    */
6065     /* empty squares. This initial position is then copied to boards[0],  */
6066     /* possibly after shuffling, so that it remains available.            */
6067
6068     gameInfo.holdingsWidth = 0; /* default board sizes */
6069     gameInfo.boardWidth    = 8;
6070     gameInfo.boardHeight   = 8;
6071     gameInfo.holdingsSize  = 0;
6072     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6073     for(i=0; i<BOARD_FILES-6; i++)
6074       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6075     initialPosition[EP_STATUS] = EP_NONE;
6076     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6077     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6078     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6079          SetCharTable(pieceNickName, appData.pieceNickNames);
6080     else SetCharTable(pieceNickName, "............");
6081     pieces = FIDEArray;
6082
6083     switch (gameInfo.variant) {
6084     case VariantFischeRandom:
6085       shuffleOpenings = TRUE;
6086       appData.fischerCastling = TRUE;
6087     default:
6088       break;
6089     case VariantShatranj:
6090       pieces = ShatranjArray;
6091       nrCastlingRights = 0;
6092       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6093       break;
6094     case VariantMakruk:
6095       pieces = makrukArray;
6096       nrCastlingRights = 0;
6097       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6098       break;
6099     case VariantASEAN:
6100       pieces = aseanArray;
6101       nrCastlingRights = 0;
6102       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6103       break;
6104     case VariantTwoKings:
6105       pieces = twoKingsArray;
6106       break;
6107     case VariantGrand:
6108       pieces = GrandArray;
6109       nrCastlingRights = 0;
6110       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6111       gameInfo.boardWidth = 10;
6112       gameInfo.boardHeight = 10;
6113       gameInfo.holdingsSize = 7;
6114       break;
6115     case VariantCapaRandom:
6116       shuffleOpenings = TRUE;
6117       appData.fischerCastling = TRUE;
6118     case VariantCapablanca:
6119       pieces = CapablancaArray;
6120       gameInfo.boardWidth = 10;
6121       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6122       break;
6123     case VariantGothic:
6124       pieces = GothicArray;
6125       gameInfo.boardWidth = 10;
6126       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6127       break;
6128     case VariantSChess:
6129       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6130       gameInfo.holdingsSize = 7;
6131       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6132       break;
6133     case VariantJanus:
6134       pieces = JanusArray;
6135       gameInfo.boardWidth = 10;
6136       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6137       nrCastlingRights = 6;
6138         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6139         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6140         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6141         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6142         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6143         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6144       break;
6145     case VariantFalcon:
6146       pieces = FalconArray;
6147       gameInfo.boardWidth = 10;
6148       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6149       break;
6150     case VariantXiangqi:
6151       pieces = XiangqiArray;
6152       gameInfo.boardWidth  = 9;
6153       gameInfo.boardHeight = 10;
6154       nrCastlingRights = 0;
6155       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6156       break;
6157     case VariantShogi:
6158       pieces = ShogiArray;
6159       gameInfo.boardWidth  = 9;
6160       gameInfo.boardHeight = 9;
6161       gameInfo.holdingsSize = 7;
6162       nrCastlingRights = 0;
6163       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6164       break;
6165     case VariantChu:
6166       pieces = ChuArray; pieceRows = 3;
6167       gameInfo.boardWidth  = 12;
6168       gameInfo.boardHeight = 12;
6169       nrCastlingRights = 0;
6170       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN:+.++.++++++++++.+++++K"
6171                                    "p.brqsexogcathd.vmlifn:+.++.++++++++++.+++++k", SUFFIXES);
6172       break;
6173     case VariantCourier:
6174       pieces = CourierArray;
6175       gameInfo.boardWidth  = 12;
6176       nrCastlingRights = 0;
6177       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6178       break;
6179     case VariantKnightmate:
6180       pieces = KnightmateArray;
6181       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6182       break;
6183     case VariantSpartan:
6184       pieces = SpartanArray;
6185       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6186       break;
6187     case VariantLion:
6188       pieces = lionArray;
6189       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6190       break;
6191     case VariantChuChess:
6192       pieces = ChuChessArray;
6193       gameInfo.boardWidth = 10;
6194       gameInfo.boardHeight = 10;
6195       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6196       break;
6197     case VariantFairy:
6198       pieces = fairyArray;
6199       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6200       break;
6201     case VariantGreat:
6202       pieces = GreatArray;
6203       gameInfo.boardWidth = 10;
6204       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6205       gameInfo.holdingsSize = 8;
6206       break;
6207     case VariantSuper:
6208       pieces = FIDEArray;
6209       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6210       gameInfo.holdingsSize = 8;
6211       startedFromSetupPosition = TRUE;
6212       break;
6213     case VariantCrazyhouse:
6214     case VariantBughouse:
6215       pieces = FIDEArray;
6216       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6217       gameInfo.holdingsSize = 5;
6218       break;
6219     case VariantWildCastle:
6220       pieces = FIDEArray;
6221       /* !!?shuffle with kings guaranteed to be on d or e file */
6222       shuffleOpenings = 1;
6223       break;
6224     case VariantNoCastle:
6225       pieces = FIDEArray;
6226       nrCastlingRights = 0;
6227       /* !!?unconstrained back-rank shuffle */
6228       shuffleOpenings = 1;
6229       break;
6230     }
6231
6232     overrule = 0;
6233     if(appData.NrFiles >= 0) {
6234         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6235         gameInfo.boardWidth = appData.NrFiles;
6236     }
6237     if(appData.NrRanks >= 0) {
6238         gameInfo.boardHeight = appData.NrRanks;
6239     }
6240     if(appData.holdingsSize >= 0) {
6241         i = appData.holdingsSize;
6242         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6243         gameInfo.holdingsSize = i;
6244     }
6245     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6246     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6247         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6248
6249     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6250     if(pawnRow < 1) pawnRow = 1;
6251     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6252        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6253     if(gameInfo.variant == VariantChu) pawnRow = 3;
6254
6255     /* User pieceToChar list overrules defaults */
6256     if(appData.pieceToCharTable != NULL)
6257         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6258
6259     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6260
6261         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6262             s = (ChessSquare) 0; /* account holding counts in guard band */
6263         for( i=0; i<BOARD_HEIGHT; i++ )
6264             initialPosition[i][j] = s;
6265
6266         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6267         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6268         initialPosition[pawnRow][j] = WhitePawn;
6269         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6270         if(gameInfo.variant == VariantXiangqi) {
6271             if(j&1) {
6272                 initialPosition[pawnRow][j] =
6273                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6274                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6275                    initialPosition[2][j] = WhiteCannon;
6276                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6277                 }
6278             }
6279         }
6280         if(gameInfo.variant == VariantChu) {
6281              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6282                initialPosition[pawnRow+1][j] = WhiteCobra,
6283                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6284              for(i=1; i<pieceRows; i++) {
6285                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6286                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6287              }
6288         }
6289         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6290             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6291                initialPosition[0][j] = WhiteRook;
6292                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6293             }
6294         }
6295         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6296     }
6297     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6298     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6299
6300             j=BOARD_LEFT+1;
6301             initialPosition[1][j] = WhiteBishop;
6302             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6303             j=BOARD_RGHT-2;
6304             initialPosition[1][j] = WhiteRook;
6305             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6306     }
6307
6308     if( nrCastlingRights == -1) {
6309         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6310         /*       This sets default castling rights from none to normal corners   */
6311         /* Variants with other castling rights must set them themselves above    */
6312         nrCastlingRights = 6;
6313
6314         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6315         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6316         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6317         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6318         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6319         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6320      }
6321
6322      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6323      if(gameInfo.variant == VariantGreat) { // promotion commoners
6324         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6325         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6326         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6327         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6328      }
6329      if( gameInfo.variant == VariantSChess ) {
6330       initialPosition[1][0] = BlackMarshall;
6331       initialPosition[2][0] = BlackAngel;
6332       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6333       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6334       initialPosition[1][1] = initialPosition[2][1] =
6335       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6336      }
6337   if (appData.debugMode) {
6338     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6339   }
6340     if(shuffleOpenings) {
6341         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6342         startedFromSetupPosition = TRUE;
6343     }
6344     if(startedFromPositionFile) {
6345       /* [HGM] loadPos: use PositionFile for every new game */
6346       CopyBoard(initialPosition, filePosition);
6347       for(i=0; i<nrCastlingRights; i++)
6348           initialRights[i] = filePosition[CASTLING][i];
6349       startedFromSetupPosition = TRUE;
6350     }
6351
6352     CopyBoard(boards[0], initialPosition);
6353
6354     if(oldx != gameInfo.boardWidth ||
6355        oldy != gameInfo.boardHeight ||
6356        oldv != gameInfo.variant ||
6357        oldh != gameInfo.holdingsWidth
6358                                          )
6359             InitDrawingSizes(-2 ,0);
6360
6361     oldv = gameInfo.variant;
6362     if (redraw)
6363       DrawPosition(TRUE, boards[currentMove]);
6364 }
6365
6366 void
6367 SendBoard (ChessProgramState *cps, int moveNum)
6368 {
6369     char message[MSG_SIZ];
6370
6371     if (cps->useSetboard) {
6372       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6373       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6374       SendToProgram(message, cps);
6375       free(fen);
6376
6377     } else {
6378       ChessSquare *bp;
6379       int i, j, left=0, right=BOARD_WIDTH;
6380       /* Kludge to set black to move, avoiding the troublesome and now
6381        * deprecated "black" command.
6382        */
6383       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6384         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6385
6386       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6387
6388       SendToProgram("edit\n", cps);
6389       SendToProgram("#\n", cps);
6390       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6391         bp = &boards[moveNum][i][left];
6392         for (j = left; j < right; j++, bp++) {
6393           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6394           if ((int) *bp < (int) BlackPawn) {
6395             if(j == BOARD_RGHT+1)
6396                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6397             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6398             if(message[0] == '+' || message[0] == '~') {
6399               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6400                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6401                         AAA + j, ONE + i);
6402             }
6403             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6404                 message[1] = BOARD_RGHT   - 1 - j + '1';
6405                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6406             }
6407             SendToProgram(message, cps);
6408           }
6409         }
6410       }
6411
6412       SendToProgram("c\n", cps);
6413       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6414         bp = &boards[moveNum][i][left];
6415         for (j = left; j < right; j++, bp++) {
6416           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6417           if (((int) *bp != (int) EmptySquare)
6418               && ((int) *bp >= (int) BlackPawn)) {
6419             if(j == BOARD_LEFT-2)
6420                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6421             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6422                     AAA + j, ONE + i);
6423             if(message[0] == '+' || message[0] == '~') {
6424               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6425                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6426                         AAA + j, ONE + i);
6427             }
6428             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6429                 message[1] = BOARD_RGHT   - 1 - j + '1';
6430                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6431             }
6432             SendToProgram(message, cps);
6433           }
6434         }
6435       }
6436
6437       SendToProgram(".\n", cps);
6438     }
6439     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6440 }
6441
6442 char exclusionHeader[MSG_SIZ];
6443 int exCnt, excludePtr;
6444 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6445 static Exclusion excluTab[200];
6446 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6447
6448 static void
6449 WriteMap (int s)
6450 {
6451     int j;
6452     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6453     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6454 }
6455
6456 static void
6457 ClearMap ()
6458 {
6459     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6460     excludePtr = 24; exCnt = 0;
6461     WriteMap(0);
6462 }
6463
6464 static void
6465 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6466 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6467     char buf[2*MOVE_LEN], *p;
6468     Exclusion *e = excluTab;
6469     int i;
6470     for(i=0; i<exCnt; i++)
6471         if(e[i].ff == fromX && e[i].fr == fromY &&
6472            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6473     if(i == exCnt) { // was not in exclude list; add it
6474         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6475         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6476             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6477             return; // abort
6478         }
6479         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6480         excludePtr++; e[i].mark = excludePtr++;
6481         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6482         exCnt++;
6483     }
6484     exclusionHeader[e[i].mark] = state;
6485 }
6486
6487 static int
6488 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6489 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6490     char buf[MSG_SIZ];
6491     int j, k;
6492     ChessMove moveType;
6493     if((signed char)promoChar == -1) { // kludge to indicate best move
6494         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6495             return 1; // if unparsable, abort
6496     }
6497     // update exclusion map (resolving toggle by consulting existing state)
6498     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6499     j = k%8; k >>= 3;
6500     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6501     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6502          excludeMap[k] |=   1<<j;
6503     else excludeMap[k] &= ~(1<<j);
6504     // update header
6505     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6506     // inform engine
6507     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6508     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6509     SendToBoth(buf);
6510     return (state == '+');
6511 }
6512
6513 static void
6514 ExcludeClick (int index)
6515 {
6516     int i, j;
6517     Exclusion *e = excluTab;
6518     if(index < 25) { // none, best or tail clicked
6519         if(index < 13) { // none: include all
6520             WriteMap(0); // clear map
6521             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6522             SendToBoth("include all\n"); // and inform engine
6523         } else if(index > 18) { // tail
6524             if(exclusionHeader[19] == '-') { // tail was excluded
6525                 SendToBoth("include all\n");
6526                 WriteMap(0); // clear map completely
6527                 // now re-exclude selected moves
6528                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6529                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6530             } else { // tail was included or in mixed state
6531                 SendToBoth("exclude all\n");
6532                 WriteMap(0xFF); // fill map completely
6533                 // now re-include selected moves
6534                 j = 0; // count them
6535                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6536                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6537                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6538             }
6539         } else { // best
6540             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6541         }
6542     } else {
6543         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6544             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6545             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6546             break;
6547         }
6548     }
6549 }
6550
6551 ChessSquare
6552 DefaultPromoChoice (int white)
6553 {
6554     ChessSquare result;
6555     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6556        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6557         result = WhiteFerz; // no choice
6558     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6559         result= WhiteKing; // in Suicide Q is the last thing we want
6560     else if(gameInfo.variant == VariantSpartan)
6561         result = white ? WhiteQueen : WhiteAngel;
6562     else result = WhiteQueen;
6563     if(!white) result = WHITE_TO_BLACK result;
6564     return result;
6565 }
6566
6567 static int autoQueen; // [HGM] oneclick
6568
6569 int
6570 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6571 {
6572     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6573     /* [HGM] add Shogi promotions */
6574     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6575     ChessSquare piece, partner;
6576     ChessMove moveType;
6577     Boolean premove;
6578
6579     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6580     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6581
6582     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6583       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6584         return FALSE;
6585
6586     piece = boards[currentMove][fromY][fromX];
6587     if(gameInfo.variant == VariantChu) {
6588         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6589         promotionZoneSize = BOARD_HEIGHT/3;
6590         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6591     } else if(gameInfo.variant == VariantShogi) {
6592         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6593         highestPromotingPiece = (int)WhiteAlfil;
6594     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6595         promotionZoneSize = 3;
6596     }
6597
6598     // Treat Lance as Pawn when it is not representing Amazon or Lance
6599     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6600         if(piece == WhiteLance) piece = WhitePawn; else
6601         if(piece == BlackLance) piece = BlackPawn;
6602     }
6603
6604     // next weed out all moves that do not touch the promotion zone at all
6605     if((int)piece >= BlackPawn) {
6606         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6607              return FALSE;
6608         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6609         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6610     } else {
6611         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6612            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6613         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6614              return FALSE;
6615     }
6616
6617     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6618
6619     // weed out mandatory Shogi promotions
6620     if(gameInfo.variant == VariantShogi) {
6621         if(piece >= BlackPawn) {
6622             if(toY == 0 && piece == BlackPawn ||
6623                toY == 0 && piece == BlackQueen ||
6624                toY <= 1 && piece == BlackKnight) {
6625                 *promoChoice = '+';
6626                 return FALSE;
6627             }
6628         } else {
6629             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6630                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6631                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6632                 *promoChoice = '+';
6633                 return FALSE;
6634             }
6635         }
6636     }
6637
6638     // weed out obviously illegal Pawn moves
6639     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6640         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6641         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6642         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6643         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6644         // note we are not allowed to test for valid (non-)capture, due to premove
6645     }
6646
6647     // we either have a choice what to promote to, or (in Shogi) whether to promote
6648     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6649        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6650         ChessSquare p=BlackFerz;  // no choice
6651         while(p < EmptySquare) {  //but make sure we use piece that exists
6652             *promoChoice = PieceToChar(p++);
6653             if(*promoChoice != '.') break;
6654         }
6655         return FALSE;
6656     }
6657     // no sense asking what we must promote to if it is going to explode...
6658     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6659         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6660         return FALSE;
6661     }
6662     // give caller the default choice even if we will not make it
6663     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6664     partner = piece; // pieces can promote if the pieceToCharTable says so
6665     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6666     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6667     if(        sweepSelect && gameInfo.variant != VariantGreat
6668                            && gameInfo.variant != VariantGrand
6669                            && gameInfo.variant != VariantSuper) return FALSE;
6670     if(autoQueen) return FALSE; // predetermined
6671
6672     // suppress promotion popup on illegal moves that are not premoves
6673     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6674               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6675     if(appData.testLegality && !premove) {
6676         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6677                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6678         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6679         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6680             return FALSE;
6681     }
6682
6683     return TRUE;
6684 }
6685
6686 int
6687 InPalace (int row, int column)
6688 {   /* [HGM] for Xiangqi */
6689     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6690          column < (BOARD_WIDTH + 4)/2 &&
6691          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6692     return FALSE;
6693 }
6694
6695 int
6696 PieceForSquare (int x, int y)
6697 {
6698   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6699      return -1;
6700   else
6701      return boards[currentMove][y][x];
6702 }
6703
6704 int
6705 OKToStartUserMove (int x, int y)
6706 {
6707     ChessSquare from_piece;
6708     int white_piece;
6709
6710     if (matchMode) return FALSE;
6711     if (gameMode == EditPosition) return TRUE;
6712
6713     if (x >= 0 && y >= 0)
6714       from_piece = boards[currentMove][y][x];
6715     else
6716       from_piece = EmptySquare;
6717
6718     if (from_piece == EmptySquare) return FALSE;
6719
6720     white_piece = (int)from_piece >= (int)WhitePawn &&
6721       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6722
6723     switch (gameMode) {
6724       case AnalyzeFile:
6725       case TwoMachinesPlay:
6726       case EndOfGame:
6727         return FALSE;
6728
6729       case IcsObserving:
6730       case IcsIdle:
6731         return FALSE;
6732
6733       case MachinePlaysWhite:
6734       case IcsPlayingBlack:
6735         if (appData.zippyPlay) return FALSE;
6736         if (white_piece) {
6737             DisplayMoveError(_("You are playing Black"));
6738             return FALSE;
6739         }
6740         break;
6741
6742       case MachinePlaysBlack:
6743       case IcsPlayingWhite:
6744         if (appData.zippyPlay) return FALSE;
6745         if (!white_piece) {
6746             DisplayMoveError(_("You are playing White"));
6747             return FALSE;
6748         }
6749         break;
6750
6751       case PlayFromGameFile:
6752             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6753       case EditGame:
6754         if (!white_piece && WhiteOnMove(currentMove)) {
6755             DisplayMoveError(_("It is White's turn"));
6756             return FALSE;
6757         }
6758         if (white_piece && !WhiteOnMove(currentMove)) {
6759             DisplayMoveError(_("It is Black's turn"));
6760             return FALSE;
6761         }
6762         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6763             /* Editing correspondence game history */
6764             /* Could disallow this or prompt for confirmation */
6765             cmailOldMove = -1;
6766         }
6767         break;
6768
6769       case BeginningOfGame:
6770         if (appData.icsActive) return FALSE;
6771         if (!appData.noChessProgram) {
6772             if (!white_piece) {
6773                 DisplayMoveError(_("You are playing White"));
6774                 return FALSE;
6775             }
6776         }
6777         break;
6778
6779       case Training:
6780         if (!white_piece && WhiteOnMove(currentMove)) {
6781             DisplayMoveError(_("It is White's turn"));
6782             return FALSE;
6783         }
6784         if (white_piece && !WhiteOnMove(currentMove)) {
6785             DisplayMoveError(_("It is Black's turn"));
6786             return FALSE;
6787         }
6788         break;
6789
6790       default:
6791       case IcsExamining:
6792         break;
6793     }
6794     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6795         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6796         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6797         && gameMode != AnalyzeFile && gameMode != Training) {
6798         DisplayMoveError(_("Displayed position is not current"));
6799         return FALSE;
6800     }
6801     return TRUE;
6802 }
6803
6804 Boolean
6805 OnlyMove (int *x, int *y, Boolean captures)
6806 {
6807     DisambiguateClosure cl;
6808     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6809     switch(gameMode) {
6810       case MachinePlaysBlack:
6811       case IcsPlayingWhite:
6812       case BeginningOfGame:
6813         if(!WhiteOnMove(currentMove)) return FALSE;
6814         break;
6815       case MachinePlaysWhite:
6816       case IcsPlayingBlack:
6817         if(WhiteOnMove(currentMove)) return FALSE;
6818         break;
6819       case EditGame:
6820         break;
6821       default:
6822         return FALSE;
6823     }
6824     cl.pieceIn = EmptySquare;
6825     cl.rfIn = *y;
6826     cl.ffIn = *x;
6827     cl.rtIn = -1;
6828     cl.ftIn = -1;
6829     cl.promoCharIn = NULLCHAR;
6830     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6831     if( cl.kind == NormalMove ||
6832         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6833         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6834         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6835       fromX = cl.ff;
6836       fromY = cl.rf;
6837       *x = cl.ft;
6838       *y = cl.rt;
6839       return TRUE;
6840     }
6841     if(cl.kind != ImpossibleMove) return FALSE;
6842     cl.pieceIn = EmptySquare;
6843     cl.rfIn = -1;
6844     cl.ffIn = -1;
6845     cl.rtIn = *y;
6846     cl.ftIn = *x;
6847     cl.promoCharIn = NULLCHAR;
6848     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6849     if( cl.kind == NormalMove ||
6850         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6851         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6852         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6853       fromX = cl.ff;
6854       fromY = cl.rf;
6855       *x = cl.ft;
6856       *y = cl.rt;
6857       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6858       return TRUE;
6859     }
6860     return FALSE;
6861 }
6862
6863 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6864 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6865 int lastLoadGameUseList = FALSE;
6866 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6867 ChessMove lastLoadGameStart = EndOfFile;
6868 int doubleClick;
6869 Boolean addToBookFlag;
6870
6871 void
6872 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6873 {
6874     ChessMove moveType;
6875     ChessSquare pup;
6876     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6877
6878     /* Check if the user is playing in turn.  This is complicated because we
6879        let the user "pick up" a piece before it is his turn.  So the piece he
6880        tried to pick up may have been captured by the time he puts it down!
6881        Therefore we use the color the user is supposed to be playing in this
6882        test, not the color of the piece that is currently on the starting
6883        square---except in EditGame mode, where the user is playing both
6884        sides; fortunately there the capture race can't happen.  (It can
6885        now happen in IcsExamining mode, but that's just too bad.  The user
6886        will get a somewhat confusing message in that case.)
6887        */
6888
6889     switch (gameMode) {
6890       case AnalyzeFile:
6891       case TwoMachinesPlay:
6892       case EndOfGame:
6893       case IcsObserving:
6894       case IcsIdle:
6895         /* We switched into a game mode where moves are not accepted,
6896            perhaps while the mouse button was down. */
6897         return;
6898
6899       case MachinePlaysWhite:
6900         /* User is moving for Black */
6901         if (WhiteOnMove(currentMove)) {
6902             DisplayMoveError(_("It is White's turn"));
6903             return;
6904         }
6905         break;
6906
6907       case MachinePlaysBlack:
6908         /* User is moving for White */
6909         if (!WhiteOnMove(currentMove)) {
6910             DisplayMoveError(_("It is Black's turn"));
6911             return;
6912         }
6913         break;
6914
6915       case PlayFromGameFile:
6916             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6917       case EditGame:
6918       case IcsExamining:
6919       case BeginningOfGame:
6920       case AnalyzeMode:
6921       case Training:
6922         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6923         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6924             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6925             /* User is moving for Black */
6926             if (WhiteOnMove(currentMove)) {
6927                 DisplayMoveError(_("It is White's turn"));
6928                 return;
6929             }
6930         } else {
6931             /* User is moving for White */
6932             if (!WhiteOnMove(currentMove)) {
6933                 DisplayMoveError(_("It is Black's turn"));
6934                 return;
6935             }
6936         }
6937         break;
6938
6939       case IcsPlayingBlack:
6940         /* User is moving for Black */
6941         if (WhiteOnMove(currentMove)) {
6942             if (!appData.premove) {
6943                 DisplayMoveError(_("It is White's turn"));
6944             } else if (toX >= 0 && toY >= 0) {
6945                 premoveToX = toX;
6946                 premoveToY = toY;
6947                 premoveFromX = fromX;
6948                 premoveFromY = fromY;
6949                 premovePromoChar = promoChar;
6950                 gotPremove = 1;
6951                 if (appData.debugMode)
6952                     fprintf(debugFP, "Got premove: fromX %d,"
6953                             "fromY %d, toX %d, toY %d\n",
6954                             fromX, fromY, toX, toY);
6955             }
6956             return;
6957         }
6958         break;
6959
6960       case IcsPlayingWhite:
6961         /* User is moving for White */
6962         if (!WhiteOnMove(currentMove)) {
6963             if (!appData.premove) {
6964                 DisplayMoveError(_("It is Black's turn"));
6965             } else if (toX >= 0 && toY >= 0) {
6966                 premoveToX = toX;
6967                 premoveToY = toY;
6968                 premoveFromX = fromX;
6969                 premoveFromY = fromY;
6970                 premovePromoChar = promoChar;
6971                 gotPremove = 1;
6972                 if (appData.debugMode)
6973                     fprintf(debugFP, "Got premove: fromX %d,"
6974                             "fromY %d, toX %d, toY %d\n",
6975                             fromX, fromY, toX, toY);
6976             }
6977             return;
6978         }
6979         break;
6980
6981       default:
6982         break;
6983
6984       case EditPosition:
6985         /* EditPosition, empty square, or different color piece;
6986            click-click move is possible */
6987         if (toX == -2 || toY == -2) {
6988             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
6989             DrawPosition(FALSE, boards[currentMove]);
6990             return;
6991         } else if (toX >= 0 && toY >= 0) {
6992             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
6993                 ChessSquare q, p = boards[0][rf][ff];
6994                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
6995                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
6996                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
6997                 if(PieceToChar(q) == '+') gatingPiece = p;
6998             }
6999             boards[0][toY][toX] = boards[0][fromY][fromX];
7000             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7001                 if(boards[0][fromY][0] != EmptySquare) {
7002                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7003                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7004                 }
7005             } else
7006             if(fromX == BOARD_RGHT+1) {
7007                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7008                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7009                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7010                 }
7011             } else
7012             boards[0][fromY][fromX] = gatingPiece;
7013             DrawPosition(FALSE, boards[currentMove]);
7014             return;
7015         }
7016         return;
7017     }
7018
7019     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7020     pup = boards[currentMove][toY][toX];
7021
7022     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7023     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7024          if( pup != EmptySquare ) return;
7025          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7026            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7027                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7028            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7029            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7030            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7031            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7032          fromY = DROP_RANK;
7033     }
7034
7035     /* [HGM] always test for legality, to get promotion info */
7036     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7037                                          fromY, fromX, toY, toX, promoChar);
7038
7039     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7040
7041     /* [HGM] but possibly ignore an IllegalMove result */
7042     if (appData.testLegality) {
7043         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7044             DisplayMoveError(_("Illegal move"));
7045             return;
7046         }
7047     }
7048
7049     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7050         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7051              ClearPremoveHighlights(); // was included
7052         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7053         return;
7054     }
7055
7056     if(addToBookFlag) { // adding moves to book
7057         char buf[MSG_SIZ], move[MSG_SIZ];
7058         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7059         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d", fromX + AAA, fromY + ONE - '0', killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0');
7060         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7061         AddBookMove(buf);
7062         addToBookFlag = FALSE;
7063         ClearHighlights();
7064         return;
7065     }
7066
7067     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7068 }
7069
7070 /* Common tail of UserMoveEvent and DropMenuEvent */
7071 int
7072 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7073 {
7074     char *bookHit = 0;
7075
7076     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7077         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7078         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7079         if(WhiteOnMove(currentMove)) {
7080             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7081         } else {
7082             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7083         }
7084     }
7085
7086     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7087        move type in caller when we know the move is a legal promotion */
7088     if(moveType == NormalMove && promoChar)
7089         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7090
7091     /* [HGM] <popupFix> The following if has been moved here from
7092        UserMoveEvent(). Because it seemed to belong here (why not allow
7093        piece drops in training games?), and because it can only be
7094        performed after it is known to what we promote. */
7095     if (gameMode == Training) {
7096       /* compare the move played on the board to the next move in the
7097        * game. If they match, display the move and the opponent's response.
7098        * If they don't match, display an error message.
7099        */
7100       int saveAnimate;
7101       Board testBoard;
7102       CopyBoard(testBoard, boards[currentMove]);
7103       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7104
7105       if (CompareBoards(testBoard, boards[currentMove+1])) {
7106         ForwardInner(currentMove+1);
7107
7108         /* Autoplay the opponent's response.
7109          * if appData.animate was TRUE when Training mode was entered,
7110          * the response will be animated.
7111          */
7112         saveAnimate = appData.animate;
7113         appData.animate = animateTraining;
7114         ForwardInner(currentMove+1);
7115         appData.animate = saveAnimate;
7116
7117         /* check for the end of the game */
7118         if (currentMove >= forwardMostMove) {
7119           gameMode = PlayFromGameFile;
7120           ModeHighlight();
7121           SetTrainingModeOff();
7122           DisplayInformation(_("End of game"));
7123         }
7124       } else {
7125         DisplayError(_("Incorrect move"), 0);
7126       }
7127       return 1;
7128     }
7129
7130   /* Ok, now we know that the move is good, so we can kill
7131      the previous line in Analysis Mode */
7132   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7133                                 && currentMove < forwardMostMove) {
7134     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7135     else forwardMostMove = currentMove;
7136   }
7137
7138   ClearMap();
7139
7140   /* If we need the chess program but it's dead, restart it */
7141   ResurrectChessProgram();
7142
7143   /* A user move restarts a paused game*/
7144   if (pausing)
7145     PauseEvent();
7146
7147   thinkOutput[0] = NULLCHAR;
7148
7149   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7150
7151   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7152     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7153     return 1;
7154   }
7155
7156   if (gameMode == BeginningOfGame) {
7157     if (appData.noChessProgram) {
7158       gameMode = EditGame;
7159       SetGameInfo();
7160     } else {
7161       char buf[MSG_SIZ];
7162       gameMode = MachinePlaysBlack;
7163       StartClocks();
7164       SetGameInfo();
7165       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7166       DisplayTitle(buf);
7167       if (first.sendName) {
7168         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7169         SendToProgram(buf, &first);
7170       }
7171       StartClocks();
7172     }
7173     ModeHighlight();
7174   }
7175
7176   /* Relay move to ICS or chess engine */
7177   if (appData.icsActive) {
7178     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7179         gameMode == IcsExamining) {
7180       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7181         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7182         SendToICS("draw ");
7183         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7184       }
7185       // also send plain move, in case ICS does not understand atomic claims
7186       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7187       ics_user_moved = 1;
7188     }
7189   } else {
7190     if (first.sendTime && (gameMode == BeginningOfGame ||
7191                            gameMode == MachinePlaysWhite ||
7192                            gameMode == MachinePlaysBlack)) {
7193       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7194     }
7195     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7196          // [HGM] book: if program might be playing, let it use book
7197         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7198         first.maybeThinking = TRUE;
7199     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7200         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7201         SendBoard(&first, currentMove+1);
7202         if(second.analyzing) {
7203             if(!second.useSetboard) SendToProgram("undo\n", &second);
7204             SendBoard(&second, currentMove+1);
7205         }
7206     } else {
7207         SendMoveToProgram(forwardMostMove-1, &first);
7208         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7209     }
7210     if (currentMove == cmailOldMove + 1) {
7211       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7212     }
7213   }
7214
7215   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7216
7217   switch (gameMode) {
7218   case EditGame:
7219     if(appData.testLegality)
7220     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7221     case MT_NONE:
7222     case MT_CHECK:
7223       break;
7224     case MT_CHECKMATE:
7225     case MT_STAINMATE:
7226       if (WhiteOnMove(currentMove)) {
7227         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7228       } else {
7229         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7230       }
7231       break;
7232     case MT_STALEMATE:
7233       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7234       break;
7235     }
7236     break;
7237
7238   case MachinePlaysBlack:
7239   case MachinePlaysWhite:
7240     /* disable certain menu options while machine is thinking */
7241     SetMachineThinkingEnables();
7242     break;
7243
7244   default:
7245     break;
7246   }
7247
7248   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7249   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7250
7251   if(bookHit) { // [HGM] book: simulate book reply
7252         static char bookMove[MSG_SIZ]; // a bit generous?
7253
7254         programStats.nodes = programStats.depth = programStats.time =
7255         programStats.score = programStats.got_only_move = 0;
7256         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7257
7258         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7259         strcat(bookMove, bookHit);
7260         HandleMachineMove(bookMove, &first);
7261   }
7262   return 1;
7263 }
7264
7265 void
7266 MarkByFEN(char *fen)
7267 {
7268         int r, f;
7269         if(!appData.markers || !appData.highlightDragging) return;
7270         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7271         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7272         while(*fen) {
7273             int s = 0;
7274             marker[r][f] = 0;
7275             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7276             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7277             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7278             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7279             if(*fen == 'T') marker[r][f++] = 0; else
7280             if(*fen == 'Y') marker[r][f++] = 1; else
7281             if(*fen == 'G') marker[r][f++] = 3; else
7282             if(*fen == 'B') marker[r][f++] = 4; else
7283             if(*fen == 'C') marker[r][f++] = 5; else
7284             if(*fen == 'M') marker[r][f++] = 6; else
7285             if(*fen == 'W') marker[r][f++] = 7; else
7286             if(*fen == 'D') marker[r][f++] = 8; else
7287             if(*fen == 'R') marker[r][f++] = 2; else {
7288                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7289               f += s; fen -= s>0;
7290             }
7291             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7292             if(r < 0) break;
7293             fen++;
7294         }
7295         DrawPosition(TRUE, NULL);
7296 }
7297
7298 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7299
7300 void
7301 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7302 {
7303     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7304     Markers *m = (Markers *) closure;
7305     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7306         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7307                          || kind == WhiteCapturesEnPassant
7308                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 1;
7309     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
7310 }
7311
7312 static int hoverSavedValid;
7313
7314 void
7315 MarkTargetSquares (int clear)
7316 {
7317   int x, y, sum=0;
7318   if(clear) { // no reason to ever suppress clearing
7319     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7320     hoverSavedValid = 0;
7321     if(!sum) return; // nothing was cleared,no redraw needed
7322   } else {
7323     int capt = 0;
7324     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7325        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7326     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7327     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7328       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7329       if(capt)
7330       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7331     }
7332   }
7333   DrawPosition(FALSE, NULL);
7334 }
7335
7336 int
7337 Explode (Board board, int fromX, int fromY, int toX, int toY)
7338 {
7339     if(gameInfo.variant == VariantAtomic &&
7340        (board[toY][toX] != EmptySquare ||                     // capture?
7341         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7342                          board[fromY][fromX] == BlackPawn   )
7343       )) {
7344         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7345         return TRUE;
7346     }
7347     return FALSE;
7348 }
7349
7350 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7351
7352 int
7353 CanPromote (ChessSquare piece, int y)
7354 {
7355         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7356         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7357         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7358         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7359            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7360            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7361          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7362         return (piece == BlackPawn && y <= zone ||
7363                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7364                 piece == BlackLance && y <= zone ||
7365                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7366 }
7367
7368 void
7369 HoverEvent (int xPix, int yPix, int x, int y)
7370 {
7371         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7372         int r, f;
7373         if(!first.highlight) return;
7374         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7375         if(x == oldX && y == oldY) return; // only do something if we enter new square
7376         oldFromX = fromX; oldFromY = fromY;
7377         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7378           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7379             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7380           hoverSavedValid = 1;
7381         } else if(oldX != x || oldY != y) {
7382           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7383           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7384           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7385             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7386           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7387             char buf[MSG_SIZ];
7388             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7389             SendToProgram(buf, &first);
7390           }
7391           oldX = x; oldY = y;
7392 //        SetHighlights(fromX, fromY, x, y);
7393         }
7394 }
7395
7396 void ReportClick(char *action, int x, int y)
7397 {
7398         char buf[MSG_SIZ]; // Inform engine of what user does
7399         int r, f;
7400         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7401           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7402             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7403         if(!first.highlight || gameMode == EditPosition) return;
7404         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7405         SendToProgram(buf, &first);
7406 }
7407
7408 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7409
7410 void
7411 LeftClick (ClickType clickType, int xPix, int yPix)
7412 {
7413     int x, y;
7414     Boolean saveAnimate;
7415     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7416     char promoChoice = NULLCHAR;
7417     ChessSquare piece;
7418     static TimeMark lastClickTime, prevClickTime;
7419
7420     x = EventToSquare(xPix, BOARD_WIDTH);
7421     y = EventToSquare(yPix, BOARD_HEIGHT);
7422     if (!flipView && y >= 0) {
7423         y = BOARD_HEIGHT - 1 - y;
7424     }
7425     if (flipView && x >= 0) {
7426         x = BOARD_WIDTH - 1 - x;
7427     }
7428
7429     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7430         static int dummy;
7431         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7432         right = TRUE;
7433         return;
7434     }
7435
7436     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7437
7438     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7439
7440     if (clickType == Press) ErrorPopDown();
7441     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7442
7443     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7444         defaultPromoChoice = promoSweep;
7445         promoSweep = EmptySquare;   // terminate sweep
7446         promoDefaultAltered = TRUE;
7447         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7448     }
7449
7450     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7451         if(clickType == Release) return; // ignore upclick of click-click destination
7452         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7453         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7454         if(gameInfo.holdingsWidth &&
7455                 (WhiteOnMove(currentMove)
7456                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7457                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7458             // click in right holdings, for determining promotion piece
7459             ChessSquare p = boards[currentMove][y][x];
7460             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7461             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7462             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7463                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7464                 fromX = fromY = -1;
7465                 return;
7466             }
7467         }
7468         DrawPosition(FALSE, boards[currentMove]);
7469         return;
7470     }
7471
7472     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7473     if(clickType == Press
7474             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7475               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7476               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7477         return;
7478
7479     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7480         // could be static click on premove from-square: abort premove
7481         gotPremove = 0;
7482         ClearPremoveHighlights();
7483     }
7484
7485     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7486         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7487
7488     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7489         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7490                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7491         defaultPromoChoice = DefaultPromoChoice(side);
7492     }
7493
7494     autoQueen = appData.alwaysPromoteToQueen;
7495
7496     if (fromX == -1) {
7497       int originalY = y;
7498       gatingPiece = EmptySquare;
7499       if (clickType != Press) {
7500         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7501             DragPieceEnd(xPix, yPix); dragging = 0;
7502             DrawPosition(FALSE, NULL);
7503         }
7504         return;
7505       }
7506       doubleClick = FALSE;
7507       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7508         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7509       }
7510       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7511       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7512          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7513          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7514             /* First square */
7515             if (OKToStartUserMove(fromX, fromY)) {
7516                 second = 0;
7517                 ReportClick("lift", x, y);
7518                 MarkTargetSquares(0);
7519                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7520                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7521                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7522                     promoSweep = defaultPromoChoice;
7523                     selectFlag = 0; lastX = xPix; lastY = yPix;
7524                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7525                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7526                 }
7527                 if (appData.highlightDragging) {
7528                     SetHighlights(fromX, fromY, -1, -1);
7529                 } else {
7530                     ClearHighlights();
7531                 }
7532             } else fromX = fromY = -1;
7533             return;
7534         }
7535     }
7536 printf("to click %d,%d\n",x,y);
7537     /* fromX != -1 */
7538     if (clickType == Press && gameMode != EditPosition) {
7539         ChessSquare fromP;
7540         ChessSquare toP;
7541         int frc;
7542
7543         // ignore off-board to clicks
7544         if(y < 0 || x < 0) return;
7545
7546         /* Check if clicking again on the same color piece */
7547         fromP = boards[currentMove][fromY][fromX];
7548         toP = boards[currentMove][y][x];
7549         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7550         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7551             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7552            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7553              WhitePawn <= toP && toP <= WhiteKing &&
7554              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7555              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7556             (BlackPawn <= fromP && fromP <= BlackKing &&
7557              BlackPawn <= toP && toP <= BlackKing &&
7558              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7559              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7560             /* Clicked again on same color piece -- changed his mind */
7561             second = (x == fromX && y == fromY);
7562             killX = killY = -1;
7563             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7564                 second = FALSE; // first double-click rather than scond click
7565                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7566             }
7567             promoDefaultAltered = FALSE;
7568             MarkTargetSquares(1);
7569            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7570             if (appData.highlightDragging) {
7571                 SetHighlights(x, y, -1, -1);
7572             } else {
7573                 ClearHighlights();
7574             }
7575             if (OKToStartUserMove(x, y)) {
7576                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7577                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7578                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7579                  gatingPiece = boards[currentMove][fromY][fromX];
7580                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7581                 fromX = x;
7582                 fromY = y; dragging = 1;
7583                 if(!second) ReportClick("lift", x, y);
7584                 MarkTargetSquares(0);
7585                 DragPieceBegin(xPix, yPix, FALSE);
7586                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7587                     promoSweep = defaultPromoChoice;
7588                     selectFlag = 0; lastX = xPix; lastY = yPix;
7589                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7590                 }
7591             }
7592            }
7593            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7594            second = FALSE;
7595         }
7596         // ignore clicks on holdings
7597         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7598     }
7599 printf("A type=%d\n",clickType);
7600
7601     if(x == fromX && y == fromY && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7602         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7603         return;
7604     }
7605
7606     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7607         DragPieceEnd(xPix, yPix); dragging = 0;
7608         if(clearFlag) {
7609             // a deferred attempt to click-click move an empty square on top of a piece
7610             boards[currentMove][y][x] = EmptySquare;
7611             ClearHighlights();
7612             DrawPosition(FALSE, boards[currentMove]);
7613             fromX = fromY = -1; clearFlag = 0;
7614             return;
7615         }
7616         if (appData.animateDragging) {
7617             /* Undo animation damage if any */
7618             DrawPosition(FALSE, NULL);
7619         }
7620         if (second) {
7621             /* Second up/down in same square; just abort move */
7622             second = 0;
7623             fromX = fromY = -1;
7624             gatingPiece = EmptySquare;
7625             MarkTargetSquares(1);
7626             ClearHighlights();
7627             gotPremove = 0;
7628             ClearPremoveHighlights();
7629         } else {
7630             /* First upclick in same square; start click-click mode */
7631             SetHighlights(x, y, -1, -1);
7632         }
7633         return;
7634     }
7635
7636     clearFlag = 0;
7637 printf("B\n");
7638     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7639        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7640         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7641         DisplayMessage(_("only marked squares are legal"),"");
7642         DrawPosition(TRUE, NULL);
7643         return; // ignore to-click
7644     }
7645 printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
7646     /* we now have a different from- and (possibly off-board) to-square */
7647     /* Completed move */
7648     if(!sweepSelecting) {
7649         toX = x;
7650         toY = y;
7651     }
7652
7653     piece = boards[currentMove][fromY][fromX];
7654
7655     saveAnimate = appData.animate;
7656     if (clickType == Press) {
7657         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7658         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7659             // must be Edit Position mode with empty-square selected
7660             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7661             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7662             return;
7663         }
7664         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7665             return;
7666         }
7667         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7668             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7669         } else
7670         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7671         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7672           if(appData.sweepSelect) {
7673             promoSweep = defaultPromoChoice;
7674             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7675             selectFlag = 0; lastX = xPix; lastY = yPix;
7676             Sweep(0); // Pawn that is going to promote: preview promotion piece
7677             sweepSelecting = 1;
7678             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7679             MarkTargetSquares(1);
7680           }
7681           return; // promo popup appears on up-click
7682         }
7683         /* Finish clickclick move */
7684         if (appData.animate || appData.highlightLastMove) {
7685             SetHighlights(fromX, fromY, toX, toY);
7686         } else {
7687             ClearHighlights();
7688         }
7689     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7690         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7691         if (appData.animate || appData.highlightLastMove) {
7692             SetHighlights(fromX, fromY, toX, toY);
7693         } else {
7694             ClearHighlights();
7695         }
7696     } else {
7697 #if 0
7698 // [HGM] this must be done after the move is made, as with arrow it could lead to a board redraw with piece still on from square
7699         /* Finish drag move */
7700         if (appData.highlightLastMove) {
7701             SetHighlights(fromX, fromY, toX, toY);
7702         } else {
7703             ClearHighlights();
7704         }
7705 #endif
7706         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7707         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7708           dragging *= 2;            // flag button-less dragging if we are dragging
7709           MarkTargetSquares(1);
7710           if(x == killX && y == killY) killX = killY = -1; else {
7711             killX = x; killY = y;     //remeber this square as intermediate
7712             ReportClick("put", x, y); // and inform engine
7713             ReportClick("lift", x, y);
7714             MarkTargetSquares(0);
7715             return;
7716           }
7717         }
7718         DragPieceEnd(xPix, yPix); dragging = 0;
7719         /* Don't animate move and drag both */
7720         appData.animate = FALSE;
7721     }
7722
7723     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7724     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7725         ChessSquare piece = boards[currentMove][fromY][fromX];
7726         if(gameMode == EditPosition && piece != EmptySquare &&
7727            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7728             int n;
7729
7730             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7731                 n = PieceToNumber(piece - (int)BlackPawn);
7732                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7733                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7734                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7735             } else
7736             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7737                 n = PieceToNumber(piece);
7738                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7739                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7740                 boards[currentMove][n][BOARD_WIDTH-2]++;
7741             }
7742             boards[currentMove][fromY][fromX] = EmptySquare;
7743         }
7744         ClearHighlights();
7745         fromX = fromY = -1;
7746         MarkTargetSquares(1);
7747         DrawPosition(TRUE, boards[currentMove]);
7748         return;
7749     }
7750
7751     // off-board moves should not be highlighted
7752     if(x < 0 || y < 0) ClearHighlights();
7753     else ReportClick("put", x, y);
7754
7755     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7756
7757     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7758
7759     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7760         SetHighlights(fromX, fromY, toX, toY);
7761         MarkTargetSquares(1);
7762         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7763             // [HGM] super: promotion to captured piece selected from holdings
7764             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7765             promotionChoice = TRUE;
7766             // kludge follows to temporarily execute move on display, without promoting yet
7767             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7768             boards[currentMove][toY][toX] = p;
7769             DrawPosition(FALSE, boards[currentMove]);
7770             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7771             boards[currentMove][toY][toX] = q;
7772             DisplayMessage("Click in holdings to choose piece", "");
7773             return;
7774         }
7775         PromotionPopUp(promoChoice);
7776     } else {
7777         int oldMove = currentMove;
7778         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7779         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7780         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7781         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7782            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7783             DrawPosition(TRUE, boards[currentMove]);
7784         MarkTargetSquares(1);
7785         fromX = fromY = -1;
7786     }
7787     appData.animate = saveAnimate;
7788     if (appData.animate || appData.animateDragging) {
7789         /* Undo animation damage if needed */
7790         DrawPosition(FALSE, NULL);
7791     }
7792 }
7793
7794 int
7795 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7796 {   // front-end-free part taken out of PieceMenuPopup
7797     int whichMenu; int xSqr, ySqr;
7798
7799     if(seekGraphUp) { // [HGM] seekgraph
7800         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7801         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7802         return -2;
7803     }
7804
7805     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7806          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7807         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7808         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7809         if(action == Press)   {
7810             originalFlip = flipView;
7811             flipView = !flipView; // temporarily flip board to see game from partners perspective
7812             DrawPosition(TRUE, partnerBoard);
7813             DisplayMessage(partnerStatus, "");
7814             partnerUp = TRUE;
7815         } else if(action == Release) {
7816             flipView = originalFlip;
7817             DrawPosition(TRUE, boards[currentMove]);
7818             partnerUp = FALSE;
7819         }
7820         return -2;
7821     }
7822
7823     xSqr = EventToSquare(x, BOARD_WIDTH);
7824     ySqr = EventToSquare(y, BOARD_HEIGHT);
7825     if (action == Release) {
7826         if(pieceSweep != EmptySquare) {
7827             EditPositionMenuEvent(pieceSweep, toX, toY);
7828             pieceSweep = EmptySquare;
7829         } else UnLoadPV(); // [HGM] pv
7830     }
7831     if (action != Press) return -2; // return code to be ignored
7832     switch (gameMode) {
7833       case IcsExamining:
7834         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7835       case EditPosition:
7836         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7837         if (xSqr < 0 || ySqr < 0) return -1;
7838         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7839         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7840         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7841         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7842         NextPiece(0);
7843         return 2; // grab
7844       case IcsObserving:
7845         if(!appData.icsEngineAnalyze) return -1;
7846       case IcsPlayingWhite:
7847       case IcsPlayingBlack:
7848         if(!appData.zippyPlay) goto noZip;
7849       case AnalyzeMode:
7850       case AnalyzeFile:
7851       case MachinePlaysWhite:
7852       case MachinePlaysBlack:
7853       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7854         if (!appData.dropMenu) {
7855           LoadPV(x, y);
7856           return 2; // flag front-end to grab mouse events
7857         }
7858         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7859            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7860       case EditGame:
7861       noZip:
7862         if (xSqr < 0 || ySqr < 0) return -1;
7863         if (!appData.dropMenu || appData.testLegality &&
7864             gameInfo.variant != VariantBughouse &&
7865             gameInfo.variant != VariantCrazyhouse) return -1;
7866         whichMenu = 1; // drop menu
7867         break;
7868       default:
7869         return -1;
7870     }
7871
7872     if (((*fromX = xSqr) < 0) ||
7873         ((*fromY = ySqr) < 0)) {
7874         *fromX = *fromY = -1;
7875         return -1;
7876     }
7877     if (flipView)
7878       *fromX = BOARD_WIDTH - 1 - *fromX;
7879     else
7880       *fromY = BOARD_HEIGHT - 1 - *fromY;
7881
7882     return whichMenu;
7883 }
7884
7885 void
7886 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7887 {
7888 //    char * hint = lastHint;
7889     FrontEndProgramStats stats;
7890
7891     stats.which = cps == &first ? 0 : 1;
7892     stats.depth = cpstats->depth;
7893     stats.nodes = cpstats->nodes;
7894     stats.score = cpstats->score;
7895     stats.time = cpstats->time;
7896     stats.pv = cpstats->movelist;
7897     stats.hint = lastHint;
7898     stats.an_move_index = 0;
7899     stats.an_move_count = 0;
7900
7901     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7902         stats.hint = cpstats->move_name;
7903         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7904         stats.an_move_count = cpstats->nr_moves;
7905     }
7906
7907     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
7908
7909     SetProgramStats( &stats );
7910 }
7911
7912 void
7913 ClearEngineOutputPane (int which)
7914 {
7915     static FrontEndProgramStats dummyStats;
7916     dummyStats.which = which;
7917     dummyStats.pv = "#";
7918     SetProgramStats( &dummyStats );
7919 }
7920
7921 #define MAXPLAYERS 500
7922
7923 char *
7924 TourneyStandings (int display)
7925 {
7926     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7927     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7928     char result, *p, *names[MAXPLAYERS];
7929
7930     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7931         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7932     names[0] = p = strdup(appData.participants);
7933     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7934
7935     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7936
7937     while(result = appData.results[nr]) {
7938         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7939         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7940         wScore = bScore = 0;
7941         switch(result) {
7942           case '+': wScore = 2; break;
7943           case '-': bScore = 2; break;
7944           case '=': wScore = bScore = 1; break;
7945           case ' ':
7946           case '*': return strdup("busy"); // tourney not finished
7947         }
7948         score[w] += wScore;
7949         score[b] += bScore;
7950         games[w]++;
7951         games[b]++;
7952         nr++;
7953     }
7954     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7955     for(w=0; w<nPlayers; w++) {
7956         bScore = -1;
7957         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7958         ranking[w] = b; points[w] = bScore; score[b] = -2;
7959     }
7960     p = malloc(nPlayers*34+1);
7961     for(w=0; w<nPlayers && w<display; w++)
7962         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7963     free(names[0]);
7964     return p;
7965 }
7966
7967 void
7968 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7969 {       // count all piece types
7970         int p, f, r;
7971         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7972         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7973         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7974                 p = board[r][f];
7975                 pCnt[p]++;
7976                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7977                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7978                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7979                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7980                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7981                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7982         }
7983 }
7984
7985 int
7986 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7987 {
7988         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7989         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7990
7991         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7992         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7993         if(myPawns == 2 && nMine == 3) // KPP
7994             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7995         if(myPawns == 1 && nMine == 2) // KP
7996             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7997         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7998             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7999         if(myPawns) return FALSE;
8000         if(pCnt[WhiteRook+side])
8001             return pCnt[BlackRook-side] ||
8002                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8003                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8004                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8005         if(pCnt[WhiteCannon+side]) {
8006             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8007             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8008         }
8009         if(pCnt[WhiteKnight+side])
8010             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8011         return FALSE;
8012 }
8013
8014 int
8015 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8016 {
8017         VariantClass v = gameInfo.variant;
8018
8019         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8020         if(v == VariantShatranj) return TRUE; // always winnable through baring
8021         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8022         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8023
8024         if(v == VariantXiangqi) {
8025                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8026
8027                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8028                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8029                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8030                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8031                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8032                 if(stale) // we have at least one last-rank P plus perhaps C
8033                     return majors // KPKX
8034                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8035                 else // KCA*E*
8036                     return pCnt[WhiteFerz+side] // KCAK
8037                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8038                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8039                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8040
8041         } else if(v == VariantKnightmate) {
8042                 if(nMine == 1) return FALSE;
8043                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8044         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8045                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8046
8047                 if(nMine == 1) return FALSE; // bare King
8048                 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
8049                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8050                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8051                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8052                 if(pCnt[WhiteKnight+side])
8053                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8054                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8055                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8056                 if(nBishops)
8057                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8058                 if(pCnt[WhiteAlfil+side])
8059                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8060                 if(pCnt[WhiteWazir+side])
8061                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8062         }
8063
8064         return TRUE;
8065 }
8066
8067 int
8068 CompareWithRights (Board b1, Board b2)
8069 {
8070     int rights = 0;
8071     if(!CompareBoards(b1, b2)) return FALSE;
8072     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8073     /* compare castling rights */
8074     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8075            rights++; /* King lost rights, while rook still had them */
8076     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8077         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8078            rights++; /* but at least one rook lost them */
8079     }
8080     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8081            rights++;
8082     if( b1[CASTLING][5] != NoRights ) {
8083         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8084            rights++;
8085     }
8086     return rights == 0;
8087 }
8088
8089 int
8090 Adjudicate (ChessProgramState *cps)
8091 {       // [HGM] some adjudications useful with buggy engines
8092         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8093         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8094         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8095         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8096         int k, drop, count = 0; static int bare = 1;
8097         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8098         Boolean canAdjudicate = !appData.icsActive;
8099
8100         // most tests only when we understand the game, i.e. legality-checking on
8101             if( appData.testLegality )
8102             {   /* [HGM] Some more adjudications for obstinate engines */
8103                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8104                 static int moveCount = 6;
8105                 ChessMove result;
8106                 char *reason = NULL;
8107
8108                 /* Count what is on board. */
8109                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8110
8111                 /* Some material-based adjudications that have to be made before stalemate test */
8112                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8113                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8114                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8115                      if(canAdjudicate && appData.checkMates) {
8116                          if(engineOpponent)
8117                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8118                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8119                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8120                          return 1;
8121                      }
8122                 }
8123
8124                 /* Bare King in Shatranj (loses) or Losers (wins) */
8125                 if( nrW == 1 || nrB == 1) {
8126                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8127                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8128                      if(canAdjudicate && appData.checkMates) {
8129                          if(engineOpponent)
8130                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8131                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8132                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8133                          return 1;
8134                      }
8135                   } else
8136                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8137                   {    /* bare King */
8138                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8139                         if(canAdjudicate && appData.checkMates) {
8140                             /* but only adjudicate if adjudication enabled */
8141                             if(engineOpponent)
8142                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8143                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8144                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8145                             return 1;
8146                         }
8147                   }
8148                 } else bare = 1;
8149
8150
8151             // don't wait for engine to announce game end if we can judge ourselves
8152             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8153               case MT_CHECK:
8154                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8155                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8156                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8157                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8158                             checkCnt++;
8159                         if(checkCnt >= 2) {
8160                             reason = "Xboard adjudication: 3rd check";
8161                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8162                             break;
8163                         }
8164                     }
8165                 }
8166               case MT_NONE:
8167               default:
8168                 break;
8169               case MT_STEALMATE:
8170               case MT_STALEMATE:
8171               case MT_STAINMATE:
8172                 reason = "Xboard adjudication: Stalemate";
8173                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8174                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8175                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8176                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8177                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8178                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8179                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8180                                                                         EP_CHECKMATE : EP_WINS);
8181                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8182                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8183                 }
8184                 break;
8185               case MT_CHECKMATE:
8186                 reason = "Xboard adjudication: Checkmate";
8187                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8188                 if(gameInfo.variant == VariantShogi) {
8189                     if(forwardMostMove > backwardMostMove
8190                        && moveList[forwardMostMove-1][1] == '@'
8191                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8192                         reason = "XBoard adjudication: pawn-drop mate";
8193                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8194                     }
8195                 }
8196                 break;
8197             }
8198
8199                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8200                     case EP_STALEMATE:
8201                         result = GameIsDrawn; break;
8202                     case EP_CHECKMATE:
8203                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8204                     case EP_WINS:
8205                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8206                     default:
8207                         result = EndOfFile;
8208                 }
8209                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8210                     if(engineOpponent)
8211                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8212                     GameEnds( result, reason, GE_XBOARD );
8213                     return 1;
8214                 }
8215
8216                 /* Next absolutely insufficient mating material. */
8217                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8218                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8219                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8220
8221                      /* always flag draws, for judging claims */
8222                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8223
8224                      if(canAdjudicate && appData.materialDraws) {
8225                          /* but only adjudicate them if adjudication enabled */
8226                          if(engineOpponent) {
8227                            SendToProgram("force\n", engineOpponent); // suppress reply
8228                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8229                          }
8230                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8231                          return 1;
8232                      }
8233                 }
8234
8235                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8236                 if(gameInfo.variant == VariantXiangqi ?
8237                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8238                  : nrW + nrB == 4 &&
8239                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8240                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8241                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8242                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8243                    ) ) {
8244                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8245                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8246                           if(engineOpponent) {
8247                             SendToProgram("force\n", engineOpponent); // suppress reply
8248                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8249                           }
8250                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8251                           return 1;
8252                      }
8253                 } else moveCount = 6;
8254             }
8255
8256         // Repetition draws and 50-move rule can be applied independently of legality testing
8257
8258                 /* Check for rep-draws */
8259                 count = 0;
8260                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8261                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8262                 for(k = forwardMostMove-2;
8263                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8264                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8265                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8266                     k-=2)
8267                 {   int rights=0;
8268                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8269                         /* compare castling rights */
8270                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8271                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8272                                 rights++; /* King lost rights, while rook still had them */
8273                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8274                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8275                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8276                                    rights++; /* but at least one rook lost them */
8277                         }
8278                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8279                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8280                                 rights++;
8281                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8282                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8283                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8284                                    rights++;
8285                         }
8286                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8287                             && appData.drawRepeats > 1) {
8288                              /* adjudicate after user-specified nr of repeats */
8289                              int result = GameIsDrawn;
8290                              char *details = "XBoard adjudication: repetition draw";
8291                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8292                                 // [HGM] xiangqi: check for forbidden perpetuals
8293                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8294                                 for(m=forwardMostMove; m>k; m-=2) {
8295                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8296                                         ourPerpetual = 0; // the current mover did not always check
8297                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8298                                         hisPerpetual = 0; // the opponent did not always check
8299                                 }
8300                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8301                                                                         ourPerpetual, hisPerpetual);
8302                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8303                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8304                                     details = "Xboard adjudication: perpetual checking";
8305                                 } else
8306                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8307                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8308                                 } else
8309                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8310                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8311                                         result = BlackWins;
8312                                         details = "Xboard adjudication: repetition";
8313                                     }
8314                                 } else // it must be XQ
8315                                 // Now check for perpetual chases
8316                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8317                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8318                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8319                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8320                                         static char resdet[MSG_SIZ];
8321                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8322                                         details = resdet;
8323                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8324                                     } else
8325                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8326                                         break; // Abort repetition-checking loop.
8327                                 }
8328                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8329                              }
8330                              if(engineOpponent) {
8331                                SendToProgram("force\n", engineOpponent); // suppress reply
8332                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8333                              }
8334                              GameEnds( result, details, GE_XBOARD );
8335                              return 1;
8336                         }
8337                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8338                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8339                     }
8340                 }
8341
8342                 /* Now we test for 50-move draws. Determine ply count */
8343                 count = forwardMostMove;
8344                 /* look for last irreversble move */
8345                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8346                     count--;
8347                 /* if we hit starting position, add initial plies */
8348                 if( count == backwardMostMove )
8349                     count -= initialRulePlies;
8350                 count = forwardMostMove - count;
8351                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8352                         // adjust reversible move counter for checks in Xiangqi
8353                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8354                         if(i < backwardMostMove) i = backwardMostMove;
8355                         while(i <= forwardMostMove) {
8356                                 lastCheck = inCheck; // check evasion does not count
8357                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8358                                 if(inCheck || lastCheck) count--; // check does not count
8359                                 i++;
8360                         }
8361                 }
8362                 if( count >= 100)
8363                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8364                          /* this is used to judge if draw claims are legal */
8365                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8366                          if(engineOpponent) {
8367                            SendToProgram("force\n", engineOpponent); // suppress reply
8368                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8369                          }
8370                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8371                          return 1;
8372                 }
8373
8374                 /* if draw offer is pending, treat it as a draw claim
8375                  * when draw condition present, to allow engines a way to
8376                  * claim draws before making their move to avoid a race
8377                  * condition occurring after their move
8378                  */
8379                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8380                          char *p = NULL;
8381                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8382                              p = "Draw claim: 50-move rule";
8383                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8384                              p = "Draw claim: 3-fold repetition";
8385                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8386                              p = "Draw claim: insufficient mating material";
8387                          if( p != NULL && canAdjudicate) {
8388                              if(engineOpponent) {
8389                                SendToProgram("force\n", engineOpponent); // suppress reply
8390                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8391                              }
8392                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8393                              return 1;
8394                          }
8395                 }
8396
8397                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8398                     if(engineOpponent) {
8399                       SendToProgram("force\n", engineOpponent); // suppress reply
8400                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8401                     }
8402                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8403                     return 1;
8404                 }
8405         return 0;
8406 }
8407
8408 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8409 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8410 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8411
8412 static int
8413 BitbaseProbe ()
8414 {
8415     int pieces[10], squares[10], cnt=0, r, f, res;
8416     static int loaded;
8417     static PPROBE_EGBB probeBB;
8418     if(!appData.testLegality) return 10;
8419     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8420     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8421     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8422     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8423         ChessSquare piece = boards[forwardMostMove][r][f];
8424         int black = (piece >= BlackPawn);
8425         int type = piece - black*BlackPawn;
8426         if(piece == EmptySquare) continue;
8427         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8428         if(type == WhiteKing) type = WhiteQueen + 1;
8429         type = egbbCode[type];
8430         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8431         pieces[cnt] = type + black*6;
8432         if(++cnt > 5) return 11;
8433     }
8434     pieces[cnt] = squares[cnt] = 0;
8435     // probe EGBB
8436     if(loaded == 2) return 13; // loading failed before
8437     if(loaded == 0) {
8438         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8439         HMODULE lib;
8440         PLOAD_EGBB loadBB;
8441         loaded = 2; // prepare for failure
8442         if(!path) return 13; // no egbb installed
8443         strncpy(buf, path + 8, MSG_SIZ);
8444         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8445         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8446         lib = LoadLibrary(buf);
8447         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8448         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8449         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8450         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8451         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8452         loaded = 1; // success!
8453     }
8454     res = probeBB(forwardMostMove & 1, pieces, squares);
8455     return res > 0 ? 1 : res < 0 ? -1 : 0;
8456 }
8457
8458 char *
8459 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8460 {   // [HGM] book: this routine intercepts moves to simulate book replies
8461     char *bookHit = NULL;
8462
8463     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8464         char buf[MSG_SIZ];
8465         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8466         SendToProgram(buf, cps);
8467     }
8468     //first determine if the incoming move brings opponent into his book
8469     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8470         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8471     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8472     if(bookHit != NULL && !cps->bookSuspend) {
8473         // make sure opponent is not going to reply after receiving move to book position
8474         SendToProgram("force\n", cps);
8475         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8476     }
8477     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8478     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8479     // now arrange restart after book miss
8480     if(bookHit) {
8481         // after a book hit we never send 'go', and the code after the call to this routine
8482         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8483         char buf[MSG_SIZ], *move = bookHit;
8484         if(cps->useSAN) {
8485             int fromX, fromY, toX, toY;
8486             char promoChar;
8487             ChessMove moveType;
8488             move = buf + 30;
8489             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8490                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8491                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8492                                     PosFlags(forwardMostMove),
8493                                     fromY, fromX, toY, toX, promoChar, move);
8494             } else {
8495                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8496                 bookHit = NULL;
8497             }
8498         }
8499         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8500         SendToProgram(buf, cps);
8501         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8502     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8503         SendToProgram("go\n", cps);
8504         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8505     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8506         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8507             SendToProgram("go\n", cps);
8508         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8509     }
8510     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8511 }
8512
8513 int
8514 LoadError (char *errmess, ChessProgramState *cps)
8515 {   // unloads engine and switches back to -ncp mode if it was first
8516     if(cps->initDone) return FALSE;
8517     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8518     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8519     cps->pr = NoProc;
8520     if(cps == &first) {
8521         appData.noChessProgram = TRUE;
8522         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8523         gameMode = BeginningOfGame; ModeHighlight();
8524         SetNCPMode();
8525     }
8526     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8527     DisplayMessage("", ""); // erase waiting message
8528     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8529     return TRUE;
8530 }
8531
8532 char *savedMessage;
8533 ChessProgramState *savedState;
8534 void
8535 DeferredBookMove (void)
8536 {
8537         if(savedState->lastPing != savedState->lastPong)
8538                     ScheduleDelayedEvent(DeferredBookMove, 10);
8539         else
8540         HandleMachineMove(savedMessage, savedState);
8541 }
8542
8543 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8544 static ChessProgramState *stalledEngine;
8545 static char stashedInputMove[MSG_SIZ];
8546
8547 void
8548 HandleMachineMove (char *message, ChessProgramState *cps)
8549 {
8550     static char firstLeg[20];
8551     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8552     char realname[MSG_SIZ];
8553     int fromX, fromY, toX, toY;
8554     ChessMove moveType;
8555     char promoChar, roar;
8556     char *p, *pv=buf1;
8557     int machineWhite, oldError;
8558     char *bookHit;
8559
8560     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8561         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8562         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8563             DisplayError(_("Invalid pairing from pairing engine"), 0);
8564             return;
8565         }
8566         pairingReceived = 1;
8567         NextMatchGame();
8568         return; // Skim the pairing messages here.
8569     }
8570
8571     oldError = cps->userError; cps->userError = 0;
8572
8573 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8574     /*
8575      * Kludge to ignore BEL characters
8576      */
8577     while (*message == '\007') message++;
8578
8579     /*
8580      * [HGM] engine debug message: ignore lines starting with '#' character
8581      */
8582     if(cps->debug && *message == '#') return;
8583
8584     /*
8585      * Look for book output
8586      */
8587     if (cps == &first && bookRequested) {
8588         if (message[0] == '\t' || message[0] == ' ') {
8589             /* Part of the book output is here; append it */
8590             strcat(bookOutput, message);
8591             strcat(bookOutput, "  \n");
8592             return;
8593         } else if (bookOutput[0] != NULLCHAR) {
8594             /* All of book output has arrived; display it */
8595             char *p = bookOutput;
8596             while (*p != NULLCHAR) {
8597                 if (*p == '\t') *p = ' ';
8598                 p++;
8599             }
8600             DisplayInformation(bookOutput);
8601             bookRequested = FALSE;
8602             /* Fall through to parse the current output */
8603         }
8604     }
8605
8606     /*
8607      * Look for machine move.
8608      */
8609     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8610         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8611     {
8612         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8613             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8614             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8615             stalledEngine = cps;
8616             if(appData.ponderNextMove) { // bring opponent out of ponder
8617                 if(gameMode == TwoMachinesPlay) {
8618                     if(cps->other->pause)
8619                         PauseEngine(cps->other);
8620                     else
8621                         SendToProgram("easy\n", cps->other);
8622                 }
8623             }
8624             StopClocks();
8625             return;
8626         }
8627
8628         /* This method is only useful on engines that support ping */
8629         if (cps->lastPing != cps->lastPong) {
8630           if (gameMode == BeginningOfGame) {
8631             /* Extra move from before last new; ignore */
8632             if (appData.debugMode) {
8633                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8634             }
8635           } else {
8636             if (appData.debugMode) {
8637                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8638                         cps->which, gameMode);
8639             }
8640
8641             SendToProgram("undo\n", cps);
8642           }
8643           return;
8644         }
8645
8646         switch (gameMode) {
8647           case BeginningOfGame:
8648             /* Extra move from before last reset; ignore */
8649             if (appData.debugMode) {
8650                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8651             }
8652             return;
8653
8654           case EndOfGame:
8655           case IcsIdle:
8656           default:
8657             /* Extra move after we tried to stop.  The mode test is
8658                not a reliable way of detecting this problem, but it's
8659                the best we can do on engines that don't support ping.
8660             */
8661             if (appData.debugMode) {
8662                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8663                         cps->which, gameMode);
8664             }
8665             SendToProgram("undo\n", cps);
8666             return;
8667
8668           case MachinePlaysWhite:
8669           case IcsPlayingWhite:
8670             machineWhite = TRUE;
8671             break;
8672
8673           case MachinePlaysBlack:
8674           case IcsPlayingBlack:
8675             machineWhite = FALSE;
8676             break;
8677
8678           case TwoMachinesPlay:
8679             machineWhite = (cps->twoMachinesColor[0] == 'w');
8680             break;
8681         }
8682         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8683             if (appData.debugMode) {
8684                 fprintf(debugFP,
8685                         "Ignoring move out of turn by %s, gameMode %d"
8686                         ", forwardMost %d\n",
8687                         cps->which, gameMode, forwardMostMove);
8688             }
8689             return;
8690         }
8691
8692         if(cps->alphaRank) AlphaRank(machineMove, 4);
8693
8694         // [HGM] lion: (some very limited) support for Alien protocol
8695         killX = killY = -1;
8696         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8697             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8698             return;
8699         }
8700         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8701             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8702             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8703         }
8704         if(firstLeg[0]) { // there was a previous leg;
8705             // only support case where same piece makes two step
8706             char buf[20], *p = machineMove+1, *q = buf+1, f;
8707             safeStrCpy(buf, machineMove, 20);
8708             while(isdigit(*q)) q++; // find start of to-square
8709             safeStrCpy(machineMove, firstLeg, 20);
8710             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8711             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8712             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8713             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8714             firstLeg[0] = NULLCHAR;
8715         }
8716
8717         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8718                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8719             /* Machine move could not be parsed; ignore it. */
8720           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8721                     machineMove, _(cps->which));
8722             DisplayMoveError(buf1);
8723             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8724                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8725             if (gameMode == TwoMachinesPlay) {
8726               GameEnds(machineWhite ? BlackWins : WhiteWins,
8727                        buf1, GE_XBOARD);
8728             }
8729             return;
8730         }
8731
8732         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8733         /* So we have to redo legality test with true e.p. status here,  */
8734         /* to make sure an illegal e.p. capture does not slip through,   */
8735         /* to cause a forfeit on a justified illegal-move complaint      */
8736         /* of the opponent.                                              */
8737         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8738            ChessMove moveType;
8739            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8740                              fromY, fromX, toY, toX, promoChar);
8741             if(moveType == IllegalMove) {
8742               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8743                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8744                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8745                            buf1, GE_XBOARD);
8746                 return;
8747            } else if(!appData.fischerCastling)
8748            /* [HGM] Kludge to handle engines that send FRC-style castling
8749               when they shouldn't (like TSCP-Gothic) */
8750            switch(moveType) {
8751              case WhiteASideCastleFR:
8752              case BlackASideCastleFR:
8753                toX+=2;
8754                currentMoveString[2]++;
8755                break;
8756              case WhiteHSideCastleFR:
8757              case BlackHSideCastleFR:
8758                toX--;
8759                currentMoveString[2]--;
8760                break;
8761              default: ; // nothing to do, but suppresses warning of pedantic compilers
8762            }
8763         }
8764         hintRequested = FALSE;
8765         lastHint[0] = NULLCHAR;
8766         bookRequested = FALSE;
8767         /* Program may be pondering now */
8768         cps->maybeThinking = TRUE;
8769         if (cps->sendTime == 2) cps->sendTime = 1;
8770         if (cps->offeredDraw) cps->offeredDraw--;
8771
8772         /* [AS] Save move info*/
8773         pvInfoList[ forwardMostMove ].score = programStats.score;
8774         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8775         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8776
8777         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8778
8779         /* Test suites abort the 'game' after one move */
8780         if(*appData.finger) {
8781            static FILE *f;
8782            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8783            if(!f) f = fopen(appData.finger, "w");
8784            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8785            else { DisplayFatalError("Bad output file", errno, 0); return; }
8786            free(fen);
8787            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8788         }
8789
8790         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8791         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8792             int count = 0;
8793
8794             while( count < adjudicateLossPlies ) {
8795                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8796
8797                 if( count & 1 ) {
8798                     score = -score; /* Flip score for winning side */
8799                 }
8800
8801                 if( score > appData.adjudicateLossThreshold ) {
8802                     break;
8803                 }
8804
8805                 count++;
8806             }
8807
8808             if( count >= adjudicateLossPlies ) {
8809                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8810
8811                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8812                     "Xboard adjudication",
8813                     GE_XBOARD );
8814
8815                 return;
8816             }
8817         }
8818
8819         if(Adjudicate(cps)) {
8820             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8821             return; // [HGM] adjudicate: for all automatic game ends
8822         }
8823
8824 #if ZIPPY
8825         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8826             first.initDone) {
8827           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8828                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8829                 SendToICS("draw ");
8830                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8831           }
8832           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8833           ics_user_moved = 1;
8834           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8835                 char buf[3*MSG_SIZ];
8836
8837                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8838                         programStats.score / 100.,
8839                         programStats.depth,
8840                         programStats.time / 100.,
8841                         (unsigned int)programStats.nodes,
8842                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8843                         programStats.movelist);
8844                 SendToICS(buf);
8845           }
8846         }
8847 #endif
8848
8849         /* [AS] Clear stats for next move */
8850         ClearProgramStats();
8851         thinkOutput[0] = NULLCHAR;
8852         hiddenThinkOutputState = 0;
8853
8854         bookHit = NULL;
8855         if (gameMode == TwoMachinesPlay) {
8856             /* [HGM] relaying draw offers moved to after reception of move */
8857             /* and interpreting offer as claim if it brings draw condition */
8858             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8859                 SendToProgram("draw\n", cps->other);
8860             }
8861             if (cps->other->sendTime) {
8862                 SendTimeRemaining(cps->other,
8863                                   cps->other->twoMachinesColor[0] == 'w');
8864             }
8865             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8866             if (firstMove && !bookHit) {
8867                 firstMove = FALSE;
8868                 if (cps->other->useColors) {
8869                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8870                 }
8871                 SendToProgram("go\n", cps->other);
8872             }
8873             cps->other->maybeThinking = TRUE;
8874         }
8875
8876         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8877
8878         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8879
8880         if (!pausing && appData.ringBellAfterMoves) {
8881             if(!roar) RingBell();
8882         }
8883
8884         /*
8885          * Reenable menu items that were disabled while
8886          * machine was thinking
8887          */
8888         if (gameMode != TwoMachinesPlay)
8889             SetUserThinkingEnables();
8890
8891         // [HGM] book: after book hit opponent has received move and is now in force mode
8892         // force the book reply into it, and then fake that it outputted this move by jumping
8893         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8894         if(bookHit) {
8895                 static char bookMove[MSG_SIZ]; // a bit generous?
8896
8897                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8898                 strcat(bookMove, bookHit);
8899                 message = bookMove;
8900                 cps = cps->other;
8901                 programStats.nodes = programStats.depth = programStats.time =
8902                 programStats.score = programStats.got_only_move = 0;
8903                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8904
8905                 if(cps->lastPing != cps->lastPong) {
8906                     savedMessage = message; // args for deferred call
8907                     savedState = cps;
8908                     ScheduleDelayedEvent(DeferredBookMove, 10);
8909                     return;
8910                 }
8911                 goto FakeBookMove;
8912         }
8913
8914         return;
8915     }
8916
8917     /* Set special modes for chess engines.  Later something general
8918      *  could be added here; for now there is just one kludge feature,
8919      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8920      *  when "xboard" is given as an interactive command.
8921      */
8922     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8923         cps->useSigint = FALSE;
8924         cps->useSigterm = FALSE;
8925     }
8926     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8927       ParseFeatures(message+8, cps);
8928       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8929     }
8930
8931     if (!strncmp(message, "setup ", 6) && 
8932         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8933           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8934                                         ) { // [HGM] allow first engine to define opening position
8935       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8936       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8937       *buf = NULLCHAR;
8938       if(sscanf(message, "setup (%s", buf) == 1) {
8939         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
8940         ASSIGN(appData.pieceToCharTable, buf);
8941       }
8942       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8943       if(dummy >= 3) {
8944         while(message[s] && message[s++] != ' ');
8945         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8946            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8947             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8948             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8949           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8950           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
8951           startedFromSetupPosition = FALSE;
8952         }
8953       }
8954       if(startedFromSetupPosition) return;
8955       ParseFEN(boards[0], &dummy, message+s, FALSE);
8956       DrawPosition(TRUE, boards[0]);
8957       CopyBoard(initialPosition, boards[0]);
8958       startedFromSetupPosition = TRUE;
8959       return;
8960     }
8961     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
8962       ChessSquare piece = WhitePawn;
8963       char *p=buf2, *q, *s = SUFFIXES, ID = *p;
8964       if(*p == '+') piece = CHUPROMOTED WhitePawn, ID = *++p;
8965       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
8966       piece += CharToPiece(ID) - WhitePawn;
8967       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
8968       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
8969       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
8970       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
8971       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
8972       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
8973                                                && gameInfo.variant != VariantGreat
8974                                                && gameInfo.variant != VariantFairy    ) return;
8975       if(piece < EmptySquare) {
8976         pieceDefs = TRUE;
8977         ASSIGN(pieceDesc[piece], buf1);
8978         if(isupper(*p) && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
8979       }
8980       return;
8981     }
8982     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8983      * want this, I was asked to put it in, and obliged.
8984      */
8985     if (!strncmp(message, "setboard ", 9)) {
8986         Board initial_position;
8987
8988         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8989
8990         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8991             DisplayError(_("Bad FEN received from engine"), 0);
8992             return ;
8993         } else {
8994            Reset(TRUE, FALSE);
8995            CopyBoard(boards[0], initial_position);
8996            initialRulePlies = FENrulePlies;
8997            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8998            else gameMode = MachinePlaysBlack;
8999            DrawPosition(FALSE, boards[currentMove]);
9000         }
9001         return;
9002     }
9003
9004     /*
9005      * Look for communication commands
9006      */
9007     if (!strncmp(message, "telluser ", 9)) {
9008         if(message[9] == '\\' && message[10] == '\\')
9009             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9010         PlayTellSound();
9011         DisplayNote(message + 9);
9012         return;
9013     }
9014     if (!strncmp(message, "tellusererror ", 14)) {
9015         cps->userError = 1;
9016         if(message[14] == '\\' && message[15] == '\\')
9017             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9018         PlayTellSound();
9019         DisplayError(message + 14, 0);
9020         return;
9021     }
9022     if (!strncmp(message, "tellopponent ", 13)) {
9023       if (appData.icsActive) {
9024         if (loggedOn) {
9025           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9026           SendToICS(buf1);
9027         }
9028       } else {
9029         DisplayNote(message + 13);
9030       }
9031       return;
9032     }
9033     if (!strncmp(message, "tellothers ", 11)) {
9034       if (appData.icsActive) {
9035         if (loggedOn) {
9036           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9037           SendToICS(buf1);
9038         }
9039       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9040       return;
9041     }
9042     if (!strncmp(message, "tellall ", 8)) {
9043       if (appData.icsActive) {
9044         if (loggedOn) {
9045           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9046           SendToICS(buf1);
9047         }
9048       } else {
9049         DisplayNote(message + 8);
9050       }
9051       return;
9052     }
9053     if (strncmp(message, "warning", 7) == 0) {
9054         /* Undocumented feature, use tellusererror in new code */
9055         DisplayError(message, 0);
9056         return;
9057     }
9058     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9059         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9060         strcat(realname, " query");
9061         AskQuestion(realname, buf2, buf1, cps->pr);
9062         return;
9063     }
9064     /* Commands from the engine directly to ICS.  We don't allow these to be
9065      *  sent until we are logged on. Crafty kibitzes have been known to
9066      *  interfere with the login process.
9067      */
9068     if (loggedOn) {
9069         if (!strncmp(message, "tellics ", 8)) {
9070             SendToICS(message + 8);
9071             SendToICS("\n");
9072             return;
9073         }
9074         if (!strncmp(message, "tellicsnoalias ", 15)) {
9075             SendToICS(ics_prefix);
9076             SendToICS(message + 15);
9077             SendToICS("\n");
9078             return;
9079         }
9080         /* The following are for backward compatibility only */
9081         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9082             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9083             SendToICS(ics_prefix);
9084             SendToICS(message);
9085             SendToICS("\n");
9086             return;
9087         }
9088     }
9089     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9090         if(initPing == cps->lastPong) {
9091             if(gameInfo.variant == VariantUnknown) {
9092                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9093                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9094                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9095             }
9096             initPing = -1;
9097         }
9098         return;
9099     }
9100     if(!strncmp(message, "highlight ", 10)) {
9101         if((appData.testLegality || *engineVariant) && appData.markers) return;
9102         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9103         return;
9104     }
9105     if(!strncmp(message, "click ", 6)) {
9106         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9107         if(appData.testLegality || !appData.oneClick) return;
9108         sscanf(message+6, "%c%d%c", &f, &y, &c);
9109         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9110         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9111         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9112         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9113         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9114         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9115             LeftClick(Release, lastLeftX, lastLeftY);
9116         controlKey  = (c == ',');
9117         LeftClick(Press, x, y);
9118         LeftClick(Release, x, y);
9119         first.highlight = f;
9120         return;
9121     }
9122     /*
9123      * If the move is illegal, cancel it and redraw the board.
9124      * Also deal with other error cases.  Matching is rather loose
9125      * here to accommodate engines written before the spec.
9126      */
9127     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9128         strncmp(message, "Error", 5) == 0) {
9129         if (StrStr(message, "name") ||
9130             StrStr(message, "rating") || StrStr(message, "?") ||
9131             StrStr(message, "result") || StrStr(message, "board") ||
9132             StrStr(message, "bk") || StrStr(message, "computer") ||
9133             StrStr(message, "variant") || StrStr(message, "hint") ||
9134             StrStr(message, "random") || StrStr(message, "depth") ||
9135             StrStr(message, "accepted")) {
9136             return;
9137         }
9138         if (StrStr(message, "protover")) {
9139           /* Program is responding to input, so it's apparently done
9140              initializing, and this error message indicates it is
9141              protocol version 1.  So we don't need to wait any longer
9142              for it to initialize and send feature commands. */
9143           FeatureDone(cps, 1);
9144           cps->protocolVersion = 1;
9145           return;
9146         }
9147         cps->maybeThinking = FALSE;
9148
9149         if (StrStr(message, "draw")) {
9150             /* Program doesn't have "draw" command */
9151             cps->sendDrawOffers = 0;
9152             return;
9153         }
9154         if (cps->sendTime != 1 &&
9155             (StrStr(message, "time") || StrStr(message, "otim"))) {
9156           /* Program apparently doesn't have "time" or "otim" command */
9157           cps->sendTime = 0;
9158           return;
9159         }
9160         if (StrStr(message, "analyze")) {
9161             cps->analysisSupport = FALSE;
9162             cps->analyzing = FALSE;
9163 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9164             EditGameEvent(); // [HGM] try to preserve loaded game
9165             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9166             DisplayError(buf2, 0);
9167             return;
9168         }
9169         if (StrStr(message, "(no matching move)st")) {
9170           /* Special kludge for GNU Chess 4 only */
9171           cps->stKludge = TRUE;
9172           SendTimeControl(cps, movesPerSession, timeControl,
9173                           timeIncrement, appData.searchDepth,
9174                           searchTime);
9175           return;
9176         }
9177         if (StrStr(message, "(no matching move)sd")) {
9178           /* Special kludge for GNU Chess 4 only */
9179           cps->sdKludge = TRUE;
9180           SendTimeControl(cps, movesPerSession, timeControl,
9181                           timeIncrement, appData.searchDepth,
9182                           searchTime);
9183           return;
9184         }
9185         if (!StrStr(message, "llegal")) {
9186             return;
9187         }
9188         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9189             gameMode == IcsIdle) return;
9190         if (forwardMostMove <= backwardMostMove) return;
9191         if (pausing) PauseEvent();
9192       if(appData.forceIllegal) {
9193             // [HGM] illegal: machine refused move; force position after move into it
9194           SendToProgram("force\n", cps);
9195           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9196                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9197                 // when black is to move, while there might be nothing on a2 or black
9198                 // might already have the move. So send the board as if white has the move.
9199                 // But first we must change the stm of the engine, as it refused the last move
9200                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9201                 if(WhiteOnMove(forwardMostMove)) {
9202                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9203                     SendBoard(cps, forwardMostMove); // kludgeless board
9204                 } else {
9205                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9206                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9207                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9208                 }
9209           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9210             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9211                  gameMode == TwoMachinesPlay)
9212               SendToProgram("go\n", cps);
9213             return;
9214       } else
9215         if (gameMode == PlayFromGameFile) {
9216             /* Stop reading this game file */
9217             gameMode = EditGame;
9218             ModeHighlight();
9219         }
9220         /* [HGM] illegal-move claim should forfeit game when Xboard */
9221         /* only passes fully legal moves                            */
9222         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9223             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9224                                 "False illegal-move claim", GE_XBOARD );
9225             return; // do not take back move we tested as valid
9226         }
9227         currentMove = forwardMostMove-1;
9228         DisplayMove(currentMove-1); /* before DisplayMoveError */
9229         SwitchClocks(forwardMostMove-1); // [HGM] race
9230         DisplayBothClocks();
9231         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9232                 parseList[currentMove], _(cps->which));
9233         DisplayMoveError(buf1);
9234         DrawPosition(FALSE, boards[currentMove]);
9235
9236         SetUserThinkingEnables();
9237         return;
9238     }
9239     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9240         /* Program has a broken "time" command that
9241            outputs a string not ending in newline.
9242            Don't use it. */
9243         cps->sendTime = 0;
9244     }
9245     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9246         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9247             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9248     }
9249
9250     /*
9251      * If chess program startup fails, exit with an error message.
9252      * Attempts to recover here are futile. [HGM] Well, we try anyway
9253      */
9254     if ((StrStr(message, "unknown host") != NULL)
9255         || (StrStr(message, "No remote directory") != NULL)
9256         || (StrStr(message, "not found") != NULL)
9257         || (StrStr(message, "No such file") != NULL)
9258         || (StrStr(message, "can't alloc") != NULL)
9259         || (StrStr(message, "Permission denied") != NULL)) {
9260
9261         cps->maybeThinking = FALSE;
9262         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9263                 _(cps->which), cps->program, cps->host, message);
9264         RemoveInputSource(cps->isr);
9265         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9266             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9267             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9268         }
9269         return;
9270     }
9271
9272     /*
9273      * Look for hint output
9274      */
9275     if (sscanf(message, "Hint: %s", buf1) == 1) {
9276         if (cps == &first && hintRequested) {
9277             hintRequested = FALSE;
9278             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9279                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9280                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9281                                     PosFlags(forwardMostMove),
9282                                     fromY, fromX, toY, toX, promoChar, buf1);
9283                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9284                 DisplayInformation(buf2);
9285             } else {
9286                 /* Hint move could not be parsed!? */
9287               snprintf(buf2, sizeof(buf2),
9288                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9289                         buf1, _(cps->which));
9290                 DisplayError(buf2, 0);
9291             }
9292         } else {
9293           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9294         }
9295         return;
9296     }
9297
9298     /*
9299      * Ignore other messages if game is not in progress
9300      */
9301     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9302         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9303
9304     /*
9305      * look for win, lose, draw, or draw offer
9306      */
9307     if (strncmp(message, "1-0", 3) == 0) {
9308         char *p, *q, *r = "";
9309         p = strchr(message, '{');
9310         if (p) {
9311             q = strchr(p, '}');
9312             if (q) {
9313                 *q = NULLCHAR;
9314                 r = p + 1;
9315             }
9316         }
9317         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9318         return;
9319     } else if (strncmp(message, "0-1", 3) == 0) {
9320         char *p, *q, *r = "";
9321         p = strchr(message, '{');
9322         if (p) {
9323             q = strchr(p, '}');
9324             if (q) {
9325                 *q = NULLCHAR;
9326                 r = p + 1;
9327             }
9328         }
9329         /* Kludge for Arasan 4.1 bug */
9330         if (strcmp(r, "Black resigns") == 0) {
9331             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9332             return;
9333         }
9334         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9335         return;
9336     } else if (strncmp(message, "1/2", 3) == 0) {
9337         char *p, *q, *r = "";
9338         p = strchr(message, '{');
9339         if (p) {
9340             q = strchr(p, '}');
9341             if (q) {
9342                 *q = NULLCHAR;
9343                 r = p + 1;
9344             }
9345         }
9346
9347         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9348         return;
9349
9350     } else if (strncmp(message, "White resign", 12) == 0) {
9351         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9352         return;
9353     } else if (strncmp(message, "Black resign", 12) == 0) {
9354         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9355         return;
9356     } else if (strncmp(message, "White matches", 13) == 0 ||
9357                strncmp(message, "Black matches", 13) == 0   ) {
9358         /* [HGM] ignore GNUShogi noises */
9359         return;
9360     } else if (strncmp(message, "White", 5) == 0 &&
9361                message[5] != '(' &&
9362                StrStr(message, "Black") == NULL) {
9363         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9364         return;
9365     } else if (strncmp(message, "Black", 5) == 0 &&
9366                message[5] != '(') {
9367         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9368         return;
9369     } else if (strcmp(message, "resign") == 0 ||
9370                strcmp(message, "computer resigns") == 0) {
9371         switch (gameMode) {
9372           case MachinePlaysBlack:
9373           case IcsPlayingBlack:
9374             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9375             break;
9376           case MachinePlaysWhite:
9377           case IcsPlayingWhite:
9378             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9379             break;
9380           case TwoMachinesPlay:
9381             if (cps->twoMachinesColor[0] == 'w')
9382               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9383             else
9384               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9385             break;
9386           default:
9387             /* can't happen */
9388             break;
9389         }
9390         return;
9391     } else if (strncmp(message, "opponent mates", 14) == 0) {
9392         switch (gameMode) {
9393           case MachinePlaysBlack:
9394           case IcsPlayingBlack:
9395             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9396             break;
9397           case MachinePlaysWhite:
9398           case IcsPlayingWhite:
9399             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9400             break;
9401           case TwoMachinesPlay:
9402             if (cps->twoMachinesColor[0] == 'w')
9403               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9404             else
9405               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9406             break;
9407           default:
9408             /* can't happen */
9409             break;
9410         }
9411         return;
9412     } else if (strncmp(message, "computer mates", 14) == 0) {
9413         switch (gameMode) {
9414           case MachinePlaysBlack:
9415           case IcsPlayingBlack:
9416             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9417             break;
9418           case MachinePlaysWhite:
9419           case IcsPlayingWhite:
9420             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9421             break;
9422           case TwoMachinesPlay:
9423             if (cps->twoMachinesColor[0] == 'w')
9424               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9425             else
9426               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9427             break;
9428           default:
9429             /* can't happen */
9430             break;
9431         }
9432         return;
9433     } else if (strncmp(message, "checkmate", 9) == 0) {
9434         if (WhiteOnMove(forwardMostMove)) {
9435             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9436         } else {
9437             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9438         }
9439         return;
9440     } else if (strstr(message, "Draw") != NULL ||
9441                strstr(message, "game is a draw") != NULL) {
9442         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9443         return;
9444     } else if (strstr(message, "offer") != NULL &&
9445                strstr(message, "draw") != NULL) {
9446 #if ZIPPY
9447         if (appData.zippyPlay && first.initDone) {
9448             /* Relay offer to ICS */
9449             SendToICS(ics_prefix);
9450             SendToICS("draw\n");
9451         }
9452 #endif
9453         cps->offeredDraw = 2; /* valid until this engine moves twice */
9454         if (gameMode == TwoMachinesPlay) {
9455             if (cps->other->offeredDraw) {
9456                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9457             /* [HGM] in two-machine mode we delay relaying draw offer      */
9458             /* until after we also have move, to see if it is really claim */
9459             }
9460         } else if (gameMode == MachinePlaysWhite ||
9461                    gameMode == MachinePlaysBlack) {
9462           if (userOfferedDraw) {
9463             DisplayInformation(_("Machine accepts your draw offer"));
9464             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9465           } else {
9466             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9467           }
9468         }
9469     }
9470
9471
9472     /*
9473      * Look for thinking output
9474      */
9475     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9476           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9477                                 ) {
9478         int plylev, mvleft, mvtot, curscore, time;
9479         char mvname[MOVE_LEN];
9480         u64 nodes; // [DM]
9481         char plyext;
9482         int ignore = FALSE;
9483         int prefixHint = FALSE;
9484         mvname[0] = NULLCHAR;
9485
9486         switch (gameMode) {
9487           case MachinePlaysBlack:
9488           case IcsPlayingBlack:
9489             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9490             break;
9491           case MachinePlaysWhite:
9492           case IcsPlayingWhite:
9493             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9494             break;
9495           case AnalyzeMode:
9496           case AnalyzeFile:
9497             break;
9498           case IcsObserving: /* [DM] icsEngineAnalyze */
9499             if (!appData.icsEngineAnalyze) ignore = TRUE;
9500             break;
9501           case TwoMachinesPlay:
9502             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9503                 ignore = TRUE;
9504             }
9505             break;
9506           default:
9507             ignore = TRUE;
9508             break;
9509         }
9510
9511         if (!ignore) {
9512             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9513             buf1[0] = NULLCHAR;
9514             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9515                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9516
9517                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9518                     nodes += u64Const(0x100000000);
9519
9520                 if (plyext != ' ' && plyext != '\t') {
9521                     time *= 100;
9522                 }
9523
9524                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9525                 if( cps->scoreIsAbsolute &&
9526                     ( gameMode == MachinePlaysBlack ||
9527                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9528                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9529                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9530                      !WhiteOnMove(currentMove)
9531                     ) )
9532                 {
9533                     curscore = -curscore;
9534                 }
9535
9536                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9537
9538                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9539                         char buf[MSG_SIZ];
9540                         FILE *f;
9541                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9542                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9543                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9544                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9545                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9546                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9547                                 fclose(f);
9548                         }
9549                         else
9550                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9551                           DisplayError(_("failed writing PV"), 0);
9552                 }
9553
9554                 tempStats.depth = plylev;
9555                 tempStats.nodes = nodes;
9556                 tempStats.time = time;
9557                 tempStats.score = curscore;
9558                 tempStats.got_only_move = 0;
9559
9560                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9561                         int ticklen;
9562
9563                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9564                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9565                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9566                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9567                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9568                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9569                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9570                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9571                 }
9572
9573                 /* Buffer overflow protection */
9574                 if (pv[0] != NULLCHAR) {
9575                     if (strlen(pv) >= sizeof(tempStats.movelist)
9576                         && appData.debugMode) {
9577                         fprintf(debugFP,
9578                                 "PV is too long; using the first %u bytes.\n",
9579                                 (unsigned) sizeof(tempStats.movelist) - 1);
9580                     }
9581
9582                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9583                 } else {
9584                     sprintf(tempStats.movelist, " no PV\n");
9585                 }
9586
9587                 if (tempStats.seen_stat) {
9588                     tempStats.ok_to_send = 1;
9589                 }
9590
9591                 if (strchr(tempStats.movelist, '(') != NULL) {
9592                     tempStats.line_is_book = 1;
9593                     tempStats.nr_moves = 0;
9594                     tempStats.moves_left = 0;
9595                 } else {
9596                     tempStats.line_is_book = 0;
9597                 }
9598
9599                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9600                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9601
9602                 SendProgramStatsToFrontend( cps, &tempStats );
9603
9604                 /*
9605                     [AS] Protect the thinkOutput buffer from overflow... this
9606                     is only useful if buf1 hasn't overflowed first!
9607                 */
9608                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9609                          plylev,
9610                          (gameMode == TwoMachinesPlay ?
9611                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9612                          ((double) curscore) / 100.0,
9613                          prefixHint ? lastHint : "",
9614                          prefixHint ? " " : "" );
9615
9616                 if( buf1[0] != NULLCHAR ) {
9617                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9618
9619                     if( strlen(pv) > max_len ) {
9620                         if( appData.debugMode) {
9621                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9622                         }
9623                         pv[max_len+1] = '\0';
9624                     }
9625
9626                     strcat( thinkOutput, pv);
9627                 }
9628
9629                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9630                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9631                     DisplayMove(currentMove - 1);
9632                 }
9633                 return;
9634
9635             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9636                 /* crafty (9.25+) says "(only move) <move>"
9637                  * if there is only 1 legal move
9638                  */
9639                 sscanf(p, "(only move) %s", buf1);
9640                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9641                 sprintf(programStats.movelist, "%s (only move)", buf1);
9642                 programStats.depth = 1;
9643                 programStats.nr_moves = 1;
9644                 programStats.moves_left = 1;
9645                 programStats.nodes = 1;
9646                 programStats.time = 1;
9647                 programStats.got_only_move = 1;
9648
9649                 /* Not really, but we also use this member to
9650                    mean "line isn't going to change" (Crafty
9651                    isn't searching, so stats won't change) */
9652                 programStats.line_is_book = 1;
9653
9654                 SendProgramStatsToFrontend( cps, &programStats );
9655
9656                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9657                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9658                     DisplayMove(currentMove - 1);
9659                 }
9660                 return;
9661             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9662                               &time, &nodes, &plylev, &mvleft,
9663                               &mvtot, mvname) >= 5) {
9664                 /* The stat01: line is from Crafty (9.29+) in response
9665                    to the "." command */
9666                 programStats.seen_stat = 1;
9667                 cps->maybeThinking = TRUE;
9668
9669                 if (programStats.got_only_move || !appData.periodicUpdates)
9670                   return;
9671
9672                 programStats.depth = plylev;
9673                 programStats.time = time;
9674                 programStats.nodes = nodes;
9675                 programStats.moves_left = mvleft;
9676                 programStats.nr_moves = mvtot;
9677                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9678                 programStats.ok_to_send = 1;
9679                 programStats.movelist[0] = '\0';
9680
9681                 SendProgramStatsToFrontend( cps, &programStats );
9682
9683                 return;
9684
9685             } else if (strncmp(message,"++",2) == 0) {
9686                 /* Crafty 9.29+ outputs this */
9687                 programStats.got_fail = 2;
9688                 return;
9689
9690             } else if (strncmp(message,"--",2) == 0) {
9691                 /* Crafty 9.29+ outputs this */
9692                 programStats.got_fail = 1;
9693                 return;
9694
9695             } else if (thinkOutput[0] != NULLCHAR &&
9696                        strncmp(message, "    ", 4) == 0) {
9697                 unsigned message_len;
9698
9699                 p = message;
9700                 while (*p && *p == ' ') p++;
9701
9702                 message_len = strlen( p );
9703
9704                 /* [AS] Avoid buffer overflow */
9705                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9706                     strcat(thinkOutput, " ");
9707                     strcat(thinkOutput, p);
9708                 }
9709
9710                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9711                     strcat(programStats.movelist, " ");
9712                     strcat(programStats.movelist, p);
9713                 }
9714
9715                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9716                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9717                     DisplayMove(currentMove - 1);
9718                 }
9719                 return;
9720             }
9721         }
9722         else {
9723             buf1[0] = NULLCHAR;
9724
9725             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9726                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9727             {
9728                 ChessProgramStats cpstats;
9729
9730                 if (plyext != ' ' && plyext != '\t') {
9731                     time *= 100;
9732                 }
9733
9734                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9735                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9736                     curscore = -curscore;
9737                 }
9738
9739                 cpstats.depth = plylev;
9740                 cpstats.nodes = nodes;
9741                 cpstats.time = time;
9742                 cpstats.score = curscore;
9743                 cpstats.got_only_move = 0;
9744                 cpstats.movelist[0] = '\0';
9745
9746                 if (buf1[0] != NULLCHAR) {
9747                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9748                 }
9749
9750                 cpstats.ok_to_send = 0;
9751                 cpstats.line_is_book = 0;
9752                 cpstats.nr_moves = 0;
9753                 cpstats.moves_left = 0;
9754
9755                 SendProgramStatsToFrontend( cps, &cpstats );
9756             }
9757         }
9758     }
9759 }
9760
9761
9762 /* Parse a game score from the character string "game", and
9763    record it as the history of the current game.  The game
9764    score is NOT assumed to start from the standard position.
9765    The display is not updated in any way.
9766    */
9767 void
9768 ParseGameHistory (char *game)
9769 {
9770     ChessMove moveType;
9771     int fromX, fromY, toX, toY, boardIndex;
9772     char promoChar;
9773     char *p, *q;
9774     char buf[MSG_SIZ];
9775
9776     if (appData.debugMode)
9777       fprintf(debugFP, "Parsing game history: %s\n", game);
9778
9779     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9780     gameInfo.site = StrSave(appData.icsHost);
9781     gameInfo.date = PGNDate();
9782     gameInfo.round = StrSave("-");
9783
9784     /* Parse out names of players */
9785     while (*game == ' ') game++;
9786     p = buf;
9787     while (*game != ' ') *p++ = *game++;
9788     *p = NULLCHAR;
9789     gameInfo.white = StrSave(buf);
9790     while (*game == ' ') game++;
9791     p = buf;
9792     while (*game != ' ' && *game != '\n') *p++ = *game++;
9793     *p = NULLCHAR;
9794     gameInfo.black = StrSave(buf);
9795
9796     /* Parse moves */
9797     boardIndex = blackPlaysFirst ? 1 : 0;
9798     yynewstr(game);
9799     for (;;) {
9800         yyboardindex = boardIndex;
9801         moveType = (ChessMove) Myylex();
9802         switch (moveType) {
9803           case IllegalMove:             /* maybe suicide chess, etc. */
9804   if (appData.debugMode) {
9805     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9806     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9807     setbuf(debugFP, NULL);
9808   }
9809           case WhitePromotion:
9810           case BlackPromotion:
9811           case WhiteNonPromotion:
9812           case BlackNonPromotion:
9813           case NormalMove:
9814           case FirstLeg:
9815           case WhiteCapturesEnPassant:
9816           case BlackCapturesEnPassant:
9817           case WhiteKingSideCastle:
9818           case WhiteQueenSideCastle:
9819           case BlackKingSideCastle:
9820           case BlackQueenSideCastle:
9821           case WhiteKingSideCastleWild:
9822           case WhiteQueenSideCastleWild:
9823           case BlackKingSideCastleWild:
9824           case BlackQueenSideCastleWild:
9825           /* PUSH Fabien */
9826           case WhiteHSideCastleFR:
9827           case WhiteASideCastleFR:
9828           case BlackHSideCastleFR:
9829           case BlackASideCastleFR:
9830           /* POP Fabien */
9831             fromX = currentMoveString[0] - AAA;
9832             fromY = currentMoveString[1] - ONE;
9833             toX = currentMoveString[2] - AAA;
9834             toY = currentMoveString[3] - ONE;
9835             promoChar = currentMoveString[4];
9836             break;
9837           case WhiteDrop:
9838           case BlackDrop:
9839             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9840             fromX = moveType == WhiteDrop ?
9841               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9842             (int) CharToPiece(ToLower(currentMoveString[0]));
9843             fromY = DROP_RANK;
9844             toX = currentMoveString[2] - AAA;
9845             toY = currentMoveString[3] - ONE;
9846             promoChar = NULLCHAR;
9847             break;
9848           case AmbiguousMove:
9849             /* bug? */
9850             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9851   if (appData.debugMode) {
9852     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9853     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9854     setbuf(debugFP, NULL);
9855   }
9856             DisplayError(buf, 0);
9857             return;
9858           case ImpossibleMove:
9859             /* bug? */
9860             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9861   if (appData.debugMode) {
9862     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9863     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9864     setbuf(debugFP, NULL);
9865   }
9866             DisplayError(buf, 0);
9867             return;
9868           case EndOfFile:
9869             if (boardIndex < backwardMostMove) {
9870                 /* Oops, gap.  How did that happen? */
9871                 DisplayError(_("Gap in move list"), 0);
9872                 return;
9873             }
9874             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9875             if (boardIndex > forwardMostMove) {
9876                 forwardMostMove = boardIndex;
9877             }
9878             return;
9879           case ElapsedTime:
9880             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9881                 strcat(parseList[boardIndex-1], " ");
9882                 strcat(parseList[boardIndex-1], yy_text);
9883             }
9884             continue;
9885           case Comment:
9886           case PGNTag:
9887           case NAG:
9888           default:
9889             /* ignore */
9890             continue;
9891           case WhiteWins:
9892           case BlackWins:
9893           case GameIsDrawn:
9894           case GameUnfinished:
9895             if (gameMode == IcsExamining) {
9896                 if (boardIndex < backwardMostMove) {
9897                     /* Oops, gap.  How did that happen? */
9898                     return;
9899                 }
9900                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9901                 return;
9902             }
9903             gameInfo.result = moveType;
9904             p = strchr(yy_text, '{');
9905             if (p == NULL) p = strchr(yy_text, '(');
9906             if (p == NULL) {
9907                 p = yy_text;
9908                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9909             } else {
9910                 q = strchr(p, *p == '{' ? '}' : ')');
9911                 if (q != NULL) *q = NULLCHAR;
9912                 p++;
9913             }
9914             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9915             gameInfo.resultDetails = StrSave(p);
9916             continue;
9917         }
9918         if (boardIndex >= forwardMostMove &&
9919             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9920             backwardMostMove = blackPlaysFirst ? 1 : 0;
9921             return;
9922         }
9923         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9924                                  fromY, fromX, toY, toX, promoChar,
9925                                  parseList[boardIndex]);
9926         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9927         /* currentMoveString is set as a side-effect of yylex */
9928         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9929         strcat(moveList[boardIndex], "\n");
9930         boardIndex++;
9931         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9932         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9933           case MT_NONE:
9934           case MT_STALEMATE:
9935           default:
9936             break;
9937           case MT_CHECK:
9938             if(!IS_SHOGI(gameInfo.variant))
9939                 strcat(parseList[boardIndex - 1], "+");
9940             break;
9941           case MT_CHECKMATE:
9942           case MT_STAINMATE:
9943             strcat(parseList[boardIndex - 1], "#");
9944             break;
9945         }
9946     }
9947 }
9948
9949
9950 /* Apply a move to the given board  */
9951 void
9952 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9953 {
9954   ChessSquare captured = board[toY][toX], piece, pawn, king, killed; int p, rookX, oldEP, epRank, berolina = 0;
9955   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9956
9957     /* [HGM] compute & store e.p. status and castling rights for new position */
9958     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9959
9960       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9961       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
9962       board[EP_STATUS] = EP_NONE;
9963       board[EP_FILE] = board[EP_RANK] = 100;
9964
9965   if (fromY == DROP_RANK) {
9966         /* must be first */
9967         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9968             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9969             return;
9970         }
9971         piece = board[toY][toX] = (ChessSquare) fromX;
9972   } else {
9973 //      ChessSquare victim;
9974       int i;
9975
9976       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9977 //           victim = board[killY][killX],
9978            killed = board[killY][killX],
9979            board[killY][killX] = EmptySquare,
9980            board[EP_STATUS] = EP_CAPTURE;
9981
9982       if( board[toY][toX] != EmptySquare ) {
9983            board[EP_STATUS] = EP_CAPTURE;
9984            if( (fromX != toX || fromY != toY) && // not igui!
9985                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9986                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9987                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9988            }
9989       }
9990
9991       pawn = board[fromY][fromX];
9992       if( pawn == WhiteLance || pawn == BlackLance ) {
9993            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
9994                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
9995                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
9996            }
9997       }
9998       if( pawn == WhitePawn ) {
9999            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10000                board[EP_STATUS] = EP_PAWN_MOVE;
10001            if( toY-fromY>=2) {
10002                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10003                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10004                         gameInfo.variant != VariantBerolina || toX < fromX)
10005                       board[EP_STATUS] = toX | berolina;
10006                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10007                         gameInfo.variant != VariantBerolina || toX > fromX)
10008                       board[EP_STATUS] = toX;
10009            }
10010       } else
10011       if( pawn == BlackPawn ) {
10012            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10013                board[EP_STATUS] = EP_PAWN_MOVE;
10014            if( toY-fromY<= -2) {
10015                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10016                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10017                         gameInfo.variant != VariantBerolina || toX < fromX)
10018                       board[EP_STATUS] = toX | berolina;
10019                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10020                         gameInfo.variant != VariantBerolina || toX > fromX)
10021                       board[EP_STATUS] = toX;
10022            }
10023        }
10024
10025        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10026        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10027        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10028        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10029
10030        for(i=0; i<nrCastlingRights; i++) {
10031            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10032               board[CASTLING][i] == toX   && castlingRank[i] == toY
10033              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10034        }
10035
10036        if(gameInfo.variant == VariantSChess) { // update virginity
10037            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10038            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10039            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10040            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10041        }
10042
10043      if (fromX == toX && fromY == toY) return;
10044
10045      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10046      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10047      if(gameInfo.variant == VariantKnightmate)
10048          king += (int) WhiteUnicorn - (int) WhiteKing;
10049
10050     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10051        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10052         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10053         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10054         board[EP_STATUS] = EP_NONE; // capture was fake!
10055     } else
10056     /* Code added by Tord: */
10057     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10058     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10059         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10060       board[EP_STATUS] = EP_NONE; // capture was fake!
10061       board[fromY][fromX] = EmptySquare;
10062       board[toY][toX] = EmptySquare;
10063       if((toX > fromX) != (piece == WhiteRook)) {
10064         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10065       } else {
10066         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10067       }
10068     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10069                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10070       board[EP_STATUS] = EP_NONE;
10071       board[fromY][fromX] = EmptySquare;
10072       board[toY][toX] = EmptySquare;
10073       if((toX > fromX) != (piece == BlackRook)) {
10074         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10075       } else {
10076         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10077       }
10078     /* End of code added by Tord */
10079
10080     } else if (board[fromY][fromX] == king
10081         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10082         && toY == fromY && toX > fromX+1) {
10083         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10084         board[fromY][toX-1] = board[fromY][rookX];
10085         board[fromY][rookX] = EmptySquare;
10086         board[fromY][fromX] = EmptySquare;
10087         board[toY][toX] = king;
10088     } else if (board[fromY][fromX] == king
10089         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10090                && toY == fromY && toX < fromX-1) {
10091         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10092         board[fromY][toX+1] = board[fromY][rookX];
10093         board[fromY][rookX] = EmptySquare;
10094         board[fromY][fromX] = EmptySquare;
10095         board[toY][toX] = king;
10096     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10097                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10098                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10099                ) {
10100         /* white pawn promotion */
10101         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10102         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10103             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10104         board[fromY][fromX] = EmptySquare;
10105     } else if ((fromY >= BOARD_HEIGHT>>1)
10106                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10107                && (toX != fromX)
10108                && gameInfo.variant != VariantXiangqi
10109                && gameInfo.variant != VariantBerolina
10110                && (pawn == WhitePawn)
10111                && (board[toY][toX] == EmptySquare)) {
10112         board[fromY][fromX] = EmptySquare;
10113         board[toY][toX] = piece;
10114         if(toY == epRank - 128 + 1)
10115             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10116         else
10117             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10118     } else if ((fromY == BOARD_HEIGHT-4)
10119                && (toX == fromX)
10120                && gameInfo.variant == VariantBerolina
10121                && (board[fromY][fromX] == WhitePawn)
10122                && (board[toY][toX] == EmptySquare)) {
10123         board[fromY][fromX] = EmptySquare;
10124         board[toY][toX] = WhitePawn;
10125         if(oldEP & EP_BEROLIN_A) {
10126                 captured = board[fromY][fromX-1];
10127                 board[fromY][fromX-1] = EmptySquare;
10128         }else{  captured = board[fromY][fromX+1];
10129                 board[fromY][fromX+1] = EmptySquare;
10130         }
10131     } else if (board[fromY][fromX] == king
10132         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10133                && toY == fromY && toX > fromX+1) {
10134         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10135         board[fromY][toX-1] = board[fromY][rookX];
10136         board[fromY][rookX] = EmptySquare;
10137         board[fromY][fromX] = EmptySquare;
10138         board[toY][toX] = king;
10139     } else if (board[fromY][fromX] == king
10140         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10141                && toY == fromY && toX < fromX-1) {
10142         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10143         board[fromY][toX+1] = board[fromY][rookX];
10144         board[fromY][rookX] = EmptySquare;
10145         board[fromY][fromX] = EmptySquare;
10146         board[toY][toX] = king;
10147     } else if (fromY == 7 && fromX == 3
10148                && board[fromY][fromX] == BlackKing
10149                && toY == 7 && toX == 5) {
10150         board[fromY][fromX] = EmptySquare;
10151         board[toY][toX] = BlackKing;
10152         board[fromY][7] = EmptySquare;
10153         board[toY][4] = BlackRook;
10154     } else if (fromY == 7 && fromX == 3
10155                && board[fromY][fromX] == BlackKing
10156                && toY == 7 && toX == 1) {
10157         board[fromY][fromX] = EmptySquare;
10158         board[toY][toX] = BlackKing;
10159         board[fromY][0] = EmptySquare;
10160         board[toY][2] = BlackRook;
10161     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10162                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10163                && toY < promoRank && promoChar
10164                ) {
10165         /* black pawn promotion */
10166         board[toY][toX] = CharToPiece(ToLower(promoChar));
10167         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10168             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10169         board[fromY][fromX] = EmptySquare;
10170     } else if ((fromY < BOARD_HEIGHT>>1)
10171                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10172                && (toX != fromX)
10173                && gameInfo.variant != VariantXiangqi
10174                && gameInfo.variant != VariantBerolina
10175                && (pawn == BlackPawn)
10176                && (board[toY][toX] == EmptySquare)) {
10177         board[fromY][fromX] = EmptySquare;
10178         board[toY][toX] = piece;
10179         if(toY == epRank - 128 - 1)
10180             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10181         else
10182             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10183     } else if ((fromY == 3)
10184                && (toX == fromX)
10185                && gameInfo.variant == VariantBerolina
10186                && (board[fromY][fromX] == BlackPawn)
10187                && (board[toY][toX] == EmptySquare)) {
10188         board[fromY][fromX] = EmptySquare;
10189         board[toY][toX] = BlackPawn;
10190         if(oldEP & EP_BEROLIN_A) {
10191                 captured = board[fromY][fromX-1];
10192                 board[fromY][fromX-1] = EmptySquare;
10193         }else{  captured = board[fromY][fromX+1];
10194                 board[fromY][fromX+1] = EmptySquare;
10195         }
10196     } else {
10197         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10198         board[fromY][fromX] = EmptySquare;
10199         board[toY][toX] = piece;
10200     }
10201   }
10202
10203     if (gameInfo.holdingsWidth != 0) {
10204
10205       /* !!A lot more code needs to be written to support holdings  */
10206       /* [HGM] OK, so I have written it. Holdings are stored in the */
10207       /* penultimate board files, so they are automaticlly stored   */
10208       /* in the game history.                                       */
10209       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10210                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10211         /* Delete from holdings, by decreasing count */
10212         /* and erasing image if necessary            */
10213         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10214         if(p < (int) BlackPawn) { /* white drop */
10215              p -= (int)WhitePawn;
10216                  p = PieceToNumber((ChessSquare)p);
10217              if(p >= gameInfo.holdingsSize) p = 0;
10218              if(--board[p][BOARD_WIDTH-2] <= 0)
10219                   board[p][BOARD_WIDTH-1] = EmptySquare;
10220              if((int)board[p][BOARD_WIDTH-2] < 0)
10221                         board[p][BOARD_WIDTH-2] = 0;
10222         } else {                  /* black drop */
10223              p -= (int)BlackPawn;
10224                  p = PieceToNumber((ChessSquare)p);
10225              if(p >= gameInfo.holdingsSize) p = 0;
10226              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10227                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10228              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10229                         board[BOARD_HEIGHT-1-p][1] = 0;
10230         }
10231       }
10232       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10233           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10234         /* [HGM] holdings: Add to holdings, if holdings exist */
10235         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10236                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10237                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10238         }
10239         p = (int) captured;
10240         if (p >= (int) BlackPawn) {
10241           p -= (int)BlackPawn;
10242           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10243                   /* Restore shogi-promoted piece to its original  first */
10244                   captured = (ChessSquare) (DEMOTED captured);
10245                   p = DEMOTED p;
10246           }
10247           p = PieceToNumber((ChessSquare)p);
10248           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10249           board[p][BOARD_WIDTH-2]++;
10250           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10251         } else {
10252           p -= (int)WhitePawn;
10253           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10254                   captured = (ChessSquare) (DEMOTED captured);
10255                   p = DEMOTED p;
10256           }
10257           p = PieceToNumber((ChessSquare)p);
10258           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10259           board[BOARD_HEIGHT-1-p][1]++;
10260           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10261         }
10262       }
10263     } else if (gameInfo.variant == VariantAtomic) {
10264       if (captured != EmptySquare) {
10265         int y, x;
10266         for (y = toY-1; y <= toY+1; y++) {
10267           for (x = toX-1; x <= toX+1; x++) {
10268             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10269                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10270               board[y][x] = EmptySquare;
10271             }
10272           }
10273         }
10274         board[toY][toX] = EmptySquare;
10275       }
10276     }
10277
10278     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10279         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10280     } else
10281     if(promoChar == '+') {
10282         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10283         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10284         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10285           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10286     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10287         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10288         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10289            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10290         board[toY][toX] = newPiece;
10291     }
10292     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10293                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10294         // [HGM] superchess: take promotion piece out of holdings
10295         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10296         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10297             if(!--board[k][BOARD_WIDTH-2])
10298                 board[k][BOARD_WIDTH-1] = EmptySquare;
10299         } else {
10300             if(!--board[BOARD_HEIGHT-1-k][1])
10301                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10302         }
10303     }
10304 }
10305
10306 /* Updates forwardMostMove */
10307 void
10308 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10309 {
10310     int x = toX, y = toY;
10311     char *s = parseList[forwardMostMove];
10312     ChessSquare p = boards[forwardMostMove][toY][toX];
10313 //    forwardMostMove++; // [HGM] bare: moved downstream
10314
10315     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10316     (void) CoordsToAlgebraic(boards[forwardMostMove],
10317                              PosFlags(forwardMostMove),
10318                              fromY, fromX, y, x, promoChar,
10319                              s);
10320     if(killX >= 0 && killY >= 0)
10321         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10322
10323     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10324         int timeLeft; static int lastLoadFlag=0; int king, piece;
10325         piece = boards[forwardMostMove][fromY][fromX];
10326         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10327         if(gameInfo.variant == VariantKnightmate)
10328             king += (int) WhiteUnicorn - (int) WhiteKing;
10329         if(forwardMostMove == 0) {
10330             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10331                 fprintf(serverMoves, "%s;", UserName());
10332             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10333                 fprintf(serverMoves, "%s;", second.tidy);
10334             fprintf(serverMoves, "%s;", first.tidy);
10335             if(gameMode == MachinePlaysWhite)
10336                 fprintf(serverMoves, "%s;", UserName());
10337             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10338                 fprintf(serverMoves, "%s;", second.tidy);
10339         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10340         lastLoadFlag = loadFlag;
10341         // print base move
10342         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10343         // print castling suffix
10344         if( toY == fromY && piece == king ) {
10345             if(toX-fromX > 1)
10346                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10347             if(fromX-toX >1)
10348                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10349         }
10350         // e.p. suffix
10351         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10352              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10353              boards[forwardMostMove][toY][toX] == EmptySquare
10354              && fromX != toX && fromY != toY)
10355                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10356         // promotion suffix
10357         if(promoChar != NULLCHAR) {
10358             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10359                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10360                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10361             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10362         }
10363         if(!loadFlag) {
10364                 char buf[MOVE_LEN*2], *p; int len;
10365             fprintf(serverMoves, "/%d/%d",
10366                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10367             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10368             else                      timeLeft = blackTimeRemaining/1000;
10369             fprintf(serverMoves, "/%d", timeLeft);
10370                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10371                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10372                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10373                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10374             fprintf(serverMoves, "/%s", buf);
10375         }
10376         fflush(serverMoves);
10377     }
10378
10379     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10380         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10381       return;
10382     }
10383     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10384     if (commentList[forwardMostMove+1] != NULL) {
10385         free(commentList[forwardMostMove+1]);
10386         commentList[forwardMostMove+1] = NULL;
10387     }
10388     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10389     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10390     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10391     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10392     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10393     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10394     adjustedClock = FALSE;
10395     gameInfo.result = GameUnfinished;
10396     if (gameInfo.resultDetails != NULL) {
10397         free(gameInfo.resultDetails);
10398         gameInfo.resultDetails = NULL;
10399     }
10400     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10401                               moveList[forwardMostMove - 1]);
10402     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10403       case MT_NONE:
10404       case MT_STALEMATE:
10405       default:
10406         break;
10407       case MT_CHECK:
10408         if(!IS_SHOGI(gameInfo.variant))
10409             strcat(parseList[forwardMostMove - 1], "+");
10410         break;
10411       case MT_CHECKMATE:
10412       case MT_STAINMATE:
10413         strcat(parseList[forwardMostMove - 1], "#");
10414         break;
10415     }
10416 }
10417
10418 /* Updates currentMove if not pausing */
10419 void
10420 ShowMove (int fromX, int fromY, int toX, int toY)
10421 {
10422     int instant = (gameMode == PlayFromGameFile) ?
10423         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10424     if(appData.noGUI) return;
10425     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10426         if (!instant) {
10427             if (forwardMostMove == currentMove + 1) {
10428                 AnimateMove(boards[forwardMostMove - 1],
10429                             fromX, fromY, toX, toY);
10430             }
10431         }
10432         currentMove = forwardMostMove;
10433     }
10434
10435     killX = killY = -1; // [HGM] lion: used up
10436
10437     if (instant) return;
10438
10439     DisplayMove(currentMove - 1);
10440     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10441             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10442                 SetHighlights(fromX, fromY, toX, toY);
10443             }
10444     }
10445     DrawPosition(FALSE, boards[currentMove]);
10446     DisplayBothClocks();
10447     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10448 }
10449
10450 void
10451 SendEgtPath (ChessProgramState *cps)
10452 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10453         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10454
10455         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10456
10457         while(*p) {
10458             char c, *q = name+1, *r, *s;
10459
10460             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10461             while(*p && *p != ',') *q++ = *p++;
10462             *q++ = ':'; *q = 0;
10463             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10464                 strcmp(name, ",nalimov:") == 0 ) {
10465                 // take nalimov path from the menu-changeable option first, if it is defined
10466               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10467                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10468             } else
10469             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10470                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10471                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10472                 s = r = StrStr(s, ":") + 1; // beginning of path info
10473                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10474                 c = *r; *r = 0;             // temporarily null-terminate path info
10475                     *--q = 0;               // strip of trailig ':' from name
10476                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10477                 *r = c;
10478                 SendToProgram(buf,cps);     // send egtbpath command for this format
10479             }
10480             if(*p == ',') p++; // read away comma to position for next format name
10481         }
10482 }
10483
10484 static int
10485 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10486 {
10487       int width = 8, height = 8, holdings = 0;             // most common sizes
10488       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10489       // correct the deviations default for each variant
10490       if( v == VariantXiangqi ) width = 9,  height = 10;
10491       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10492       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10493       if( v == VariantCapablanca || v == VariantCapaRandom ||
10494           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10495                                 width = 10;
10496       if( v == VariantCourier ) width = 12;
10497       if( v == VariantSuper )                            holdings = 8;
10498       if( v == VariantGreat )   width = 10,              holdings = 8;
10499       if( v == VariantSChess )                           holdings = 7;
10500       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10501       if( v == VariantChuChess) width = 10, height = 10;
10502       if( v == VariantChu )     width = 12, height = 12;
10503       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10504              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10505              holdingsSize >= 0 && holdingsSize != holdings;
10506 }
10507
10508 char variantError[MSG_SIZ];
10509
10510 char *
10511 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10512 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10513       char *p, *variant = VariantName(v);
10514       static char b[MSG_SIZ];
10515       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10516            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10517                                                holdingsSize, variant); // cook up sized variant name
10518            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10519            if(StrStr(list, b) == NULL) {
10520                // specific sized variant not known, check if general sizing allowed
10521                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10522                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10523                             boardWidth, boardHeight, holdingsSize, engine);
10524                    return NULL;
10525                }
10526                /* [HGM] here we really should compare with the maximum supported board size */
10527            }
10528       } else snprintf(b, MSG_SIZ,"%s", variant);
10529       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10530       p = StrStr(list, b);
10531       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10532       if(p == NULL) {
10533           // occurs not at all in list, or only as sub-string
10534           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10535           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10536               int l = strlen(variantError);
10537               char *q;
10538               while(p != list && p[-1] != ',') p--;
10539               q = strchr(p, ',');
10540               if(q) *q = NULLCHAR;
10541               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10542               if(q) *q= ',';
10543           }
10544           return NULL;
10545       }
10546       return b;
10547 }
10548
10549 void
10550 InitChessProgram (ChessProgramState *cps, int setup)
10551 /* setup needed to setup FRC opening position */
10552 {
10553     char buf[MSG_SIZ], *b;
10554     if (appData.noChessProgram) return;
10555     hintRequested = FALSE;
10556     bookRequested = FALSE;
10557
10558     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10559     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10560     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10561     if(cps->memSize) { /* [HGM] memory */
10562       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10563         SendToProgram(buf, cps);
10564     }
10565     SendEgtPath(cps); /* [HGM] EGT */
10566     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10567       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10568         SendToProgram(buf, cps);
10569     }
10570
10571     setboardSpoiledMachineBlack = FALSE;
10572     SendToProgram(cps->initString, cps);
10573     if (gameInfo.variant != VariantNormal &&
10574         gameInfo.variant != VariantLoadable
10575         /* [HGM] also send variant if board size non-standard */
10576         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10577
10578       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10579                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10580       if (b == NULL) {
10581         VariantClass v;
10582         char c, *q = cps->variants, *p = strchr(q, ',');
10583         if(p) *p = NULLCHAR;
10584         v = StringToVariant(q);
10585         DisplayError(variantError, 0);
10586         if(v != VariantUnknown && cps == &first) {
10587             int w, h, s;
10588             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10589                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10590             ASSIGN(appData.variant, q);
10591             Reset(TRUE, FALSE);
10592         }
10593         if(p) *p = ',';
10594         return;
10595       }
10596
10597       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10598       SendToProgram(buf, cps);
10599     }
10600     currentlyInitializedVariant = gameInfo.variant;
10601
10602     /* [HGM] send opening position in FRC to first engine */
10603     if(setup) {
10604           SendToProgram("force\n", cps);
10605           SendBoard(cps, 0);
10606           /* engine is now in force mode! Set flag to wake it up after first move. */
10607           setboardSpoiledMachineBlack = 1;
10608     }
10609
10610     if (cps->sendICS) {
10611       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10612       SendToProgram(buf, cps);
10613     }
10614     cps->maybeThinking = FALSE;
10615     cps->offeredDraw = 0;
10616     if (!appData.icsActive) {
10617         SendTimeControl(cps, movesPerSession, timeControl,
10618                         timeIncrement, appData.searchDepth,
10619                         searchTime);
10620     }
10621     if (appData.showThinking
10622         // [HGM] thinking: four options require thinking output to be sent
10623         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10624                                 ) {
10625         SendToProgram("post\n", cps);
10626     }
10627     SendToProgram("hard\n", cps);
10628     if (!appData.ponderNextMove) {
10629         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10630            it without being sure what state we are in first.  "hard"
10631            is not a toggle, so that one is OK.
10632          */
10633         SendToProgram("easy\n", cps);
10634     }
10635     if (cps->usePing) {
10636       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10637       SendToProgram(buf, cps);
10638     }
10639     cps->initDone = TRUE;
10640     ClearEngineOutputPane(cps == &second);
10641 }
10642
10643
10644 void
10645 ResendOptions (ChessProgramState *cps)
10646 { // send the stored value of the options
10647   int i;
10648   char buf[MSG_SIZ];
10649   Option *opt = cps->option;
10650   for(i=0; i<cps->nrOptions; i++, opt++) {
10651       switch(opt->type) {
10652         case Spin:
10653         case Slider:
10654         case CheckBox:
10655             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10656           break;
10657         case ComboBox:
10658           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10659           break;
10660         default:
10661             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10662           break;
10663         case Button:
10664         case SaveButton:
10665           continue;
10666       }
10667       SendToProgram(buf, cps);
10668   }
10669 }
10670
10671 void
10672 StartChessProgram (ChessProgramState *cps)
10673 {
10674     char buf[MSG_SIZ];
10675     int err;
10676
10677     if (appData.noChessProgram) return;
10678     cps->initDone = FALSE;
10679
10680     if (strcmp(cps->host, "localhost") == 0) {
10681         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10682     } else if (*appData.remoteShell == NULLCHAR) {
10683         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10684     } else {
10685         if (*appData.remoteUser == NULLCHAR) {
10686           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10687                     cps->program);
10688         } else {
10689           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10690                     cps->host, appData.remoteUser, cps->program);
10691         }
10692         err = StartChildProcess(buf, "", &cps->pr);
10693     }
10694
10695     if (err != 0) {
10696       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10697         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10698         if(cps != &first) return;
10699         appData.noChessProgram = TRUE;
10700         ThawUI();
10701         SetNCPMode();
10702 //      DisplayFatalError(buf, err, 1);
10703 //      cps->pr = NoProc;
10704 //      cps->isr = NULL;
10705         return;
10706     }
10707
10708     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10709     if (cps->protocolVersion > 1) {
10710       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10711       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10712         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10713         cps->comboCnt = 0;  //                and values of combo boxes
10714       }
10715       SendToProgram(buf, cps);
10716       if(cps->reload) ResendOptions(cps);
10717     } else {
10718       SendToProgram("xboard\n", cps);
10719     }
10720 }
10721
10722 void
10723 TwoMachinesEventIfReady P((void))
10724 {
10725   static int curMess = 0;
10726   if (first.lastPing != first.lastPong) {
10727     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10728     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10729     return;
10730   }
10731   if (second.lastPing != second.lastPong) {
10732     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10733     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10734     return;
10735   }
10736   DisplayMessage("", ""); curMess = 0;
10737   TwoMachinesEvent();
10738 }
10739
10740 char *
10741 MakeName (char *template)
10742 {
10743     time_t clock;
10744     struct tm *tm;
10745     static char buf[MSG_SIZ];
10746     char *p = buf;
10747     int i;
10748
10749     clock = time((time_t *)NULL);
10750     tm = localtime(&clock);
10751
10752     while(*p++ = *template++) if(p[-1] == '%') {
10753         switch(*template++) {
10754           case 0:   *p = 0; return buf;
10755           case 'Y': i = tm->tm_year+1900; break;
10756           case 'y': i = tm->tm_year-100; break;
10757           case 'M': i = tm->tm_mon+1; break;
10758           case 'd': i = tm->tm_mday; break;
10759           case 'h': i = tm->tm_hour; break;
10760           case 'm': i = tm->tm_min; break;
10761           case 's': i = tm->tm_sec; break;
10762           default:  i = 0;
10763         }
10764         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10765     }
10766     return buf;
10767 }
10768
10769 int
10770 CountPlayers (char *p)
10771 {
10772     int n = 0;
10773     while(p = strchr(p, '\n')) p++, n++; // count participants
10774     return n;
10775 }
10776
10777 FILE *
10778 WriteTourneyFile (char *results, FILE *f)
10779 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10780     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10781     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10782         // create a file with tournament description
10783         fprintf(f, "-participants {%s}\n", appData.participants);
10784         fprintf(f, "-seedBase %d\n", appData.seedBase);
10785         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10786         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10787         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10788         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10789         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10790         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10791         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10792         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10793         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10794         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10795         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10796         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10797         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10798         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10799         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10800         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10801         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10802         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10803         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10804         fprintf(f, "-smpCores %d\n", appData.smpCores);
10805         if(searchTime > 0)
10806                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10807         else {
10808                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10809                 fprintf(f, "-tc %s\n", appData.timeControl);
10810                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10811         }
10812         fprintf(f, "-results \"%s\"\n", results);
10813     }
10814     return f;
10815 }
10816
10817 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10818
10819 void
10820 Substitute (char *participants, int expunge)
10821 {
10822     int i, changed, changes=0, nPlayers=0;
10823     char *p, *q, *r, buf[MSG_SIZ];
10824     if(participants == NULL) return;
10825     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10826     r = p = participants; q = appData.participants;
10827     while(*p && *p == *q) {
10828         if(*p == '\n') r = p+1, nPlayers++;
10829         p++; q++;
10830     }
10831     if(*p) { // difference
10832         while(*p && *p++ != '\n');
10833         while(*q && *q++ != '\n');
10834       changed = nPlayers;
10835         changes = 1 + (strcmp(p, q) != 0);
10836     }
10837     if(changes == 1) { // a single engine mnemonic was changed
10838         q = r; while(*q) nPlayers += (*q++ == '\n');
10839         p = buf; while(*r && (*p = *r++) != '\n') p++;
10840         *p = NULLCHAR;
10841         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10842         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10843         if(mnemonic[i]) { // The substitute is valid
10844             FILE *f;
10845             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10846                 flock(fileno(f), LOCK_EX);
10847                 ParseArgsFromFile(f);
10848                 fseek(f, 0, SEEK_SET);
10849                 FREE(appData.participants); appData.participants = participants;
10850                 if(expunge) { // erase results of replaced engine
10851                     int len = strlen(appData.results), w, b, dummy;
10852                     for(i=0; i<len; i++) {
10853                         Pairing(i, nPlayers, &w, &b, &dummy);
10854                         if((w == changed || b == changed) && appData.results[i] == '*') {
10855                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10856                             fclose(f);
10857                             return;
10858                         }
10859                     }
10860                     for(i=0; i<len; i++) {
10861                         Pairing(i, nPlayers, &w, &b, &dummy);
10862                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10863                     }
10864                 }
10865                 WriteTourneyFile(appData.results, f);
10866                 fclose(f); // release lock
10867                 return;
10868             }
10869         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10870     }
10871     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10872     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10873     free(participants);
10874     return;
10875 }
10876
10877 int
10878 CheckPlayers (char *participants)
10879 {
10880         int i;
10881         char buf[MSG_SIZ], *p;
10882         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10883         while(p = strchr(participants, '\n')) {
10884             *p = NULLCHAR;
10885             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10886             if(!mnemonic[i]) {
10887                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10888                 *p = '\n';
10889                 DisplayError(buf, 0);
10890                 return 1;
10891             }
10892             *p = '\n';
10893             participants = p + 1;
10894         }
10895         return 0;
10896 }
10897
10898 int
10899 CreateTourney (char *name)
10900 {
10901         FILE *f;
10902         if(matchMode && strcmp(name, appData.tourneyFile)) {
10903              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10904         }
10905         if(name[0] == NULLCHAR) {
10906             if(appData.participants[0])
10907                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10908             return 0;
10909         }
10910         f = fopen(name, "r");
10911         if(f) { // file exists
10912             ASSIGN(appData.tourneyFile, name);
10913             ParseArgsFromFile(f); // parse it
10914         } else {
10915             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10916             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10917                 DisplayError(_("Not enough participants"), 0);
10918                 return 0;
10919             }
10920             if(CheckPlayers(appData.participants)) return 0;
10921             ASSIGN(appData.tourneyFile, name);
10922             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10923             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10924         }
10925         fclose(f);
10926         appData.noChessProgram = FALSE;
10927         appData.clockMode = TRUE;
10928         SetGNUMode();
10929         return 1;
10930 }
10931
10932 int
10933 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10934 {
10935     char buf[MSG_SIZ], *p, *q;
10936     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10937     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10938     skip = !all && group[0]; // if group requested, we start in skip mode
10939     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10940         p = names; q = buf; header = 0;
10941         while(*p && *p != '\n') *q++ = *p++;
10942         *q = 0;
10943         if(*p == '\n') p++;
10944         if(buf[0] == '#') {
10945             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10946             depth++; // we must be entering a new group
10947             if(all) continue; // suppress printing group headers when complete list requested
10948             header = 1;
10949             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10950         }
10951         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10952         if(engineList[i]) free(engineList[i]);
10953         engineList[i] = strdup(buf);
10954         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10955         if(engineMnemonic[i]) free(engineMnemonic[i]);
10956         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10957             strcat(buf, " (");
10958             sscanf(q + 8, "%s", buf + strlen(buf));
10959             strcat(buf, ")");
10960         }
10961         engineMnemonic[i] = strdup(buf);
10962         i++;
10963     }
10964     engineList[i] = engineMnemonic[i] = NULL;
10965     return i;
10966 }
10967
10968 // following implemented as macro to avoid type limitations
10969 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10970
10971 void
10972 SwapEngines (int n)
10973 {   // swap settings for first engine and other engine (so far only some selected options)
10974     int h;
10975     char *p;
10976     if(n == 0) return;
10977     SWAP(directory, p)
10978     SWAP(chessProgram, p)
10979     SWAP(isUCI, h)
10980     SWAP(hasOwnBookUCI, h)
10981     SWAP(protocolVersion, h)
10982     SWAP(reuse, h)
10983     SWAP(scoreIsAbsolute, h)
10984     SWAP(timeOdds, h)
10985     SWAP(logo, p)
10986     SWAP(pgnName, p)
10987     SWAP(pvSAN, h)
10988     SWAP(engOptions, p)
10989     SWAP(engInitString, p)
10990     SWAP(computerString, p)
10991     SWAP(features, p)
10992     SWAP(fenOverride, p)
10993     SWAP(NPS, h)
10994     SWAP(accumulateTC, h)
10995     SWAP(drawDepth, h)
10996     SWAP(host, p)
10997     SWAP(pseudo, h)
10998 }
10999
11000 int
11001 GetEngineLine (char *s, int n)
11002 {
11003     int i;
11004     char buf[MSG_SIZ];
11005     extern char *icsNames;
11006     if(!s || !*s) return 0;
11007     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11008     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11009     if(!mnemonic[i]) return 0;
11010     if(n == 11) return 1; // just testing if there was a match
11011     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11012     if(n == 1) SwapEngines(n);
11013     ParseArgsFromString(buf);
11014     if(n == 1) SwapEngines(n);
11015     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11016         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11017         ParseArgsFromString(buf);
11018     }
11019     return 1;
11020 }
11021
11022 int
11023 SetPlayer (int player, char *p)
11024 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11025     int i;
11026     char buf[MSG_SIZ], *engineName;
11027     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11028     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11029     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11030     if(mnemonic[i]) {
11031         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11032         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11033         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11034         ParseArgsFromString(buf);
11035     } else { // no engine with this nickname is installed!
11036         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11037         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11038         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11039         ModeHighlight();
11040         DisplayError(buf, 0);
11041         return 0;
11042     }
11043     free(engineName);
11044     return i;
11045 }
11046
11047 char *recentEngines;
11048
11049 void
11050 RecentEngineEvent (int nr)
11051 {
11052     int n;
11053 //    SwapEngines(1); // bump first to second
11054 //    ReplaceEngine(&second, 1); // and load it there
11055     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11056     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11057     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11058         ReplaceEngine(&first, 0);
11059         FloatToFront(&appData.recentEngineList, command[n]);
11060     }
11061 }
11062
11063 int
11064 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11065 {   // determine players from game number
11066     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11067
11068     if(appData.tourneyType == 0) {
11069         roundsPerCycle = (nPlayers - 1) | 1;
11070         pairingsPerRound = nPlayers / 2;
11071     } else if(appData.tourneyType > 0) {
11072         roundsPerCycle = nPlayers - appData.tourneyType;
11073         pairingsPerRound = appData.tourneyType;
11074     }
11075     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11076     gamesPerCycle = gamesPerRound * roundsPerCycle;
11077     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11078     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11079     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11080     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11081     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11082     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11083
11084     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11085     if(appData.roundSync) *syncInterval = gamesPerRound;
11086
11087     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11088
11089     if(appData.tourneyType == 0) {
11090         if(curPairing == (nPlayers-1)/2 ) {
11091             *whitePlayer = curRound;
11092             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11093         } else {
11094             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11095             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11096             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11097             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11098         }
11099     } else if(appData.tourneyType > 1) {
11100         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11101         *whitePlayer = curRound + appData.tourneyType;
11102     } else if(appData.tourneyType > 0) {
11103         *whitePlayer = curPairing;
11104         *blackPlayer = curRound + appData.tourneyType;
11105     }
11106
11107     // take care of white/black alternation per round.
11108     // For cycles and games this is already taken care of by default, derived from matchGame!
11109     return curRound & 1;
11110 }
11111
11112 int
11113 NextTourneyGame (int nr, int *swapColors)
11114 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11115     char *p, *q;
11116     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11117     FILE *tf;
11118     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11119     tf = fopen(appData.tourneyFile, "r");
11120     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11121     ParseArgsFromFile(tf); fclose(tf);
11122     InitTimeControls(); // TC might be altered from tourney file
11123
11124     nPlayers = CountPlayers(appData.participants); // count participants
11125     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11126     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11127
11128     if(syncInterval) {
11129         p = q = appData.results;
11130         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11131         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11132             DisplayMessage(_("Waiting for other game(s)"),"");
11133             waitingForGame = TRUE;
11134             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11135             return 0;
11136         }
11137         waitingForGame = FALSE;
11138     }
11139
11140     if(appData.tourneyType < 0) {
11141         if(nr>=0 && !pairingReceived) {
11142             char buf[1<<16];
11143             if(pairing.pr == NoProc) {
11144                 if(!appData.pairingEngine[0]) {
11145                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11146                     return 0;
11147                 }
11148                 StartChessProgram(&pairing); // starts the pairing engine
11149             }
11150             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11151             SendToProgram(buf, &pairing);
11152             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11153             SendToProgram(buf, &pairing);
11154             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11155         }
11156         pairingReceived = 0;                              // ... so we continue here
11157         *swapColors = 0;
11158         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11159         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11160         matchGame = 1; roundNr = nr / syncInterval + 1;
11161     }
11162
11163     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11164
11165     // redefine engines, engine dir, etc.
11166     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11167     if(first.pr == NoProc) {
11168       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11169       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11170     }
11171     if(second.pr == NoProc) {
11172       SwapEngines(1);
11173       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11174       SwapEngines(1);         // and make that valid for second engine by swapping
11175       InitEngine(&second, 1);
11176     }
11177     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11178     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11179     return OK;
11180 }
11181
11182 void
11183 NextMatchGame ()
11184 {   // performs game initialization that does not invoke engines, and then tries to start the game
11185     int res, firstWhite, swapColors = 0;
11186     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11187     if(matchMode && appData.debugMode) { // [HGM] debug split: game is part of a match; we might have to create a debug file just for this game
11188         char buf[MSG_SIZ];
11189         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11190         if(strcmp(buf, currentDebugFile)) { // name has changed
11191             FILE *f = fopen(buf, "w");
11192             if(f) { // if opening the new file failed, just keep using the old one
11193                 ASSIGN(currentDebugFile, buf);
11194                 fclose(debugFP);
11195                 debugFP = f;
11196             }
11197             if(appData.serverFileName) {
11198                 if(serverFP) fclose(serverFP);
11199                 serverFP = fopen(appData.serverFileName, "w");
11200                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11201                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11202             }
11203         }
11204     }
11205     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11206     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11207     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11208     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11209     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11210     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11211     Reset(FALSE, first.pr != NoProc);
11212     res = LoadGameOrPosition(matchGame); // setup game
11213     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11214     if(!res) return; // abort when bad game/pos file
11215     TwoMachinesEvent();
11216 }
11217
11218 void
11219 UserAdjudicationEvent (int result)
11220 {
11221     ChessMove gameResult = GameIsDrawn;
11222
11223     if( result > 0 ) {
11224         gameResult = WhiteWins;
11225     }
11226     else if( result < 0 ) {
11227         gameResult = BlackWins;
11228     }
11229
11230     if( gameMode == TwoMachinesPlay ) {
11231         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11232     }
11233 }
11234
11235
11236 // [HGM] save: calculate checksum of game to make games easily identifiable
11237 int
11238 StringCheckSum (char *s)
11239 {
11240         int i = 0;
11241         if(s==NULL) return 0;
11242         while(*s) i = i*259 + *s++;
11243         return i;
11244 }
11245
11246 int
11247 GameCheckSum ()
11248 {
11249         int i, sum=0;
11250         for(i=backwardMostMove; i<forwardMostMove; i++) {
11251                 sum += pvInfoList[i].depth;
11252                 sum += StringCheckSum(parseList[i]);
11253                 sum += StringCheckSum(commentList[i]);
11254                 sum *= 261;
11255         }
11256         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11257         return sum + StringCheckSum(commentList[i]);
11258 } // end of save patch
11259
11260 void
11261 GameEnds (ChessMove result, char *resultDetails, int whosays)
11262 {
11263     GameMode nextGameMode;
11264     int isIcsGame;
11265     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11266
11267     if(endingGame) return; /* [HGM] crash: forbid recursion */
11268     endingGame = 1;
11269     if(twoBoards) { // [HGM] dual: switch back to one board
11270         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11271         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11272     }
11273     if (appData.debugMode) {
11274       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11275               result, resultDetails ? resultDetails : "(null)", whosays);
11276     }
11277
11278     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11279
11280     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11281
11282     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11283         /* If we are playing on ICS, the server decides when the
11284            game is over, but the engine can offer to draw, claim
11285            a draw, or resign.
11286          */
11287 #if ZIPPY
11288         if (appData.zippyPlay && first.initDone) {
11289             if (result == GameIsDrawn) {
11290                 /* In case draw still needs to be claimed */
11291                 SendToICS(ics_prefix);
11292                 SendToICS("draw\n");
11293             } else if (StrCaseStr(resultDetails, "resign")) {
11294                 SendToICS(ics_prefix);
11295                 SendToICS("resign\n");
11296             }
11297         }
11298 #endif
11299         endingGame = 0; /* [HGM] crash */
11300         return;
11301     }
11302
11303     /* If we're loading the game from a file, stop */
11304     if (whosays == GE_FILE) {
11305       (void) StopLoadGameTimer();
11306       gameFileFP = NULL;
11307     }
11308
11309     /* Cancel draw offers */
11310     first.offeredDraw = second.offeredDraw = 0;
11311
11312     /* If this is an ICS game, only ICS can really say it's done;
11313        if not, anyone can. */
11314     isIcsGame = (gameMode == IcsPlayingWhite ||
11315                  gameMode == IcsPlayingBlack ||
11316                  gameMode == IcsObserving    ||
11317                  gameMode == IcsExamining);
11318
11319     if (!isIcsGame || whosays == GE_ICS) {
11320         /* OK -- not an ICS game, or ICS said it was done */
11321         StopClocks();
11322         if (!isIcsGame && !appData.noChessProgram)
11323           SetUserThinkingEnables();
11324
11325         /* [HGM] if a machine claims the game end we verify this claim */
11326         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11327             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11328                 char claimer;
11329                 ChessMove trueResult = (ChessMove) -1;
11330
11331                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11332                                             first.twoMachinesColor[0] :
11333                                             second.twoMachinesColor[0] ;
11334
11335                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11336                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11337                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11338                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11339                 } else
11340                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11341                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11342                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11343                 } else
11344                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11345                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11346                 }
11347
11348                 // now verify win claims, but not in drop games, as we don't understand those yet
11349                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11350                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11351                     (result == WhiteWins && claimer == 'w' ||
11352                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11353                       if (appData.debugMode) {
11354                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11355                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11356                       }
11357                       if(result != trueResult) {
11358                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11359                               result = claimer == 'w' ? BlackWins : WhiteWins;
11360                               resultDetails = buf;
11361                       }
11362                 } else
11363                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11364                     && (forwardMostMove <= backwardMostMove ||
11365                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11366                         (claimer=='b')==(forwardMostMove&1))
11367                                                                                   ) {
11368                       /* [HGM] verify: draws that were not flagged are false claims */
11369                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11370                       result = claimer == 'w' ? BlackWins : WhiteWins;
11371                       resultDetails = buf;
11372                 }
11373                 /* (Claiming a loss is accepted no questions asked!) */
11374             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11375                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11376                 result = GameUnfinished;
11377                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11378             }
11379             /* [HGM] bare: don't allow bare King to win */
11380             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11381                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11382                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11383                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11384                && result != GameIsDrawn)
11385             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11386                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11387                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11388                         if(p >= 0 && p <= (int)WhiteKing) k++;
11389                 }
11390                 if (appData.debugMode) {
11391                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11392                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11393                 }
11394                 if(k <= 1) {
11395                         result = GameIsDrawn;
11396                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11397                         resultDetails = buf;
11398                 }
11399             }
11400         }
11401
11402
11403         if(serverMoves != NULL && !loadFlag) { char c = '=';
11404             if(result==WhiteWins) c = '+';
11405             if(result==BlackWins) c = '-';
11406             if(resultDetails != NULL)
11407                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11408         }
11409         if (resultDetails != NULL) {
11410             gameInfo.result = result;
11411             gameInfo.resultDetails = StrSave(resultDetails);
11412
11413             /* display last move only if game was not loaded from file */
11414             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11415                 DisplayMove(currentMove - 1);
11416
11417             if (forwardMostMove != 0) {
11418                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11419                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11420                                                                 ) {
11421                     if (*appData.saveGameFile != NULLCHAR) {
11422                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11423                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11424                         else
11425                         SaveGameToFile(appData.saveGameFile, TRUE);
11426                     } else if (appData.autoSaveGames) {
11427                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11428                     }
11429                     if (*appData.savePositionFile != NULLCHAR) {
11430                         SavePositionToFile(appData.savePositionFile);
11431                     }
11432                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11433                 }
11434             }
11435
11436             /* Tell program how game ended in case it is learning */
11437             /* [HGM] Moved this to after saving the PGN, just in case */
11438             /* engine died and we got here through time loss. In that */
11439             /* case we will get a fatal error writing the pipe, which */
11440             /* would otherwise lose us the PGN.                       */
11441             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11442             /* output during GameEnds should never be fatal anymore   */
11443             if (gameMode == MachinePlaysWhite ||
11444                 gameMode == MachinePlaysBlack ||
11445                 gameMode == TwoMachinesPlay ||
11446                 gameMode == IcsPlayingWhite ||
11447                 gameMode == IcsPlayingBlack ||
11448                 gameMode == BeginningOfGame) {
11449                 char buf[MSG_SIZ];
11450                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11451                         resultDetails);
11452                 if (first.pr != NoProc) {
11453                     SendToProgram(buf, &first);
11454                 }
11455                 if (second.pr != NoProc &&
11456                     gameMode == TwoMachinesPlay) {
11457                     SendToProgram(buf, &second);
11458                 }
11459             }
11460         }
11461
11462         if (appData.icsActive) {
11463             if (appData.quietPlay &&
11464                 (gameMode == IcsPlayingWhite ||
11465                  gameMode == IcsPlayingBlack)) {
11466                 SendToICS(ics_prefix);
11467                 SendToICS("set shout 1\n");
11468             }
11469             nextGameMode = IcsIdle;
11470             ics_user_moved = FALSE;
11471             /* clean up premove.  It's ugly when the game has ended and the
11472              * premove highlights are still on the board.
11473              */
11474             if (gotPremove) {
11475               gotPremove = FALSE;
11476               ClearPremoveHighlights();
11477               DrawPosition(FALSE, boards[currentMove]);
11478             }
11479             if (whosays == GE_ICS) {
11480                 switch (result) {
11481                 case WhiteWins:
11482                     if (gameMode == IcsPlayingWhite)
11483                         PlayIcsWinSound();
11484                     else if(gameMode == IcsPlayingBlack)
11485                         PlayIcsLossSound();
11486                     break;
11487                 case BlackWins:
11488                     if (gameMode == IcsPlayingBlack)
11489                         PlayIcsWinSound();
11490                     else if(gameMode == IcsPlayingWhite)
11491                         PlayIcsLossSound();
11492                     break;
11493                 case GameIsDrawn:
11494                     PlayIcsDrawSound();
11495                     break;
11496                 default:
11497                     PlayIcsUnfinishedSound();
11498                 }
11499             }
11500             if(appData.quitNext) { ExitEvent(0); return; }
11501         } else if (gameMode == EditGame ||
11502                    gameMode == PlayFromGameFile ||
11503                    gameMode == AnalyzeMode ||
11504                    gameMode == AnalyzeFile) {
11505             nextGameMode = gameMode;
11506         } else {
11507             nextGameMode = EndOfGame;
11508         }
11509         pausing = FALSE;
11510         ModeHighlight();
11511     } else {
11512         nextGameMode = gameMode;
11513     }
11514
11515     if (appData.noChessProgram) {
11516         gameMode = nextGameMode;
11517         ModeHighlight();
11518         endingGame = 0; /* [HGM] crash */
11519         return;
11520     }
11521
11522     if (first.reuse) {
11523         /* Put first chess program into idle state */
11524         if (first.pr != NoProc &&
11525             (gameMode == MachinePlaysWhite ||
11526              gameMode == MachinePlaysBlack ||
11527              gameMode == TwoMachinesPlay ||
11528              gameMode == IcsPlayingWhite ||
11529              gameMode == IcsPlayingBlack ||
11530              gameMode == BeginningOfGame)) {
11531             SendToProgram("force\n", &first);
11532             if (first.usePing) {
11533               char buf[MSG_SIZ];
11534               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11535               SendToProgram(buf, &first);
11536             }
11537         }
11538     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11539         /* Kill off first chess program */
11540         if (first.isr != NULL)
11541           RemoveInputSource(first.isr);
11542         first.isr = NULL;
11543
11544         if (first.pr != NoProc) {
11545             ExitAnalyzeMode();
11546             DoSleep( appData.delayBeforeQuit );
11547             SendToProgram("quit\n", &first);
11548             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11549             first.reload = TRUE;
11550         }
11551         first.pr = NoProc;
11552     }
11553     if (second.reuse) {
11554         /* Put second chess program into idle state */
11555         if (second.pr != NoProc &&
11556             gameMode == TwoMachinesPlay) {
11557             SendToProgram("force\n", &second);
11558             if (second.usePing) {
11559               char buf[MSG_SIZ];
11560               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11561               SendToProgram(buf, &second);
11562             }
11563         }
11564     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11565         /* Kill off second chess program */
11566         if (second.isr != NULL)
11567           RemoveInputSource(second.isr);
11568         second.isr = NULL;
11569
11570         if (second.pr != NoProc) {
11571             DoSleep( appData.delayBeforeQuit );
11572             SendToProgram("quit\n", &second);
11573             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11574             second.reload = TRUE;
11575         }
11576         second.pr = NoProc;
11577     }
11578
11579     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11580         char resChar = '=';
11581         switch (result) {
11582         case WhiteWins:
11583           resChar = '+';
11584           if (first.twoMachinesColor[0] == 'w') {
11585             first.matchWins++;
11586           } else {
11587             second.matchWins++;
11588           }
11589           break;
11590         case BlackWins:
11591           resChar = '-';
11592           if (first.twoMachinesColor[0] == 'b') {
11593             first.matchWins++;
11594           } else {
11595             second.matchWins++;
11596           }
11597           break;
11598         case GameUnfinished:
11599           resChar = ' ';
11600         default:
11601           break;
11602         }
11603
11604         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11605         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11606             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11607             ReserveGame(nextGame, resChar); // sets nextGame
11608             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11609             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11610         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11611
11612         if (nextGame <= appData.matchGames && !abortMatch) {
11613             gameMode = nextGameMode;
11614             matchGame = nextGame; // this will be overruled in tourney mode!
11615             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11616             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11617             endingGame = 0; /* [HGM] crash */
11618             return;
11619         } else {
11620             gameMode = nextGameMode;
11621             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11622                      first.tidy, second.tidy,
11623                      first.matchWins, second.matchWins,
11624                      appData.matchGames - (first.matchWins + second.matchWins));
11625             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11626             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11627             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11628             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11629                 first.twoMachinesColor = "black\n";
11630                 second.twoMachinesColor = "white\n";
11631             } else {
11632                 first.twoMachinesColor = "white\n";
11633                 second.twoMachinesColor = "black\n";
11634             }
11635         }
11636     }
11637     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11638         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11639       ExitAnalyzeMode();
11640     gameMode = nextGameMode;
11641     ModeHighlight();
11642     endingGame = 0;  /* [HGM] crash */
11643     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11644         if(matchMode == TRUE) { // match through command line: exit with or without popup
11645             if(ranking) {
11646                 ToNrEvent(forwardMostMove);
11647                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11648                 else ExitEvent(0);
11649             } else DisplayFatalError(buf, 0, 0);
11650         } else { // match through menu; just stop, with or without popup
11651             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11652             ModeHighlight();
11653             if(ranking){
11654                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11655             } else DisplayNote(buf);
11656       }
11657       if(ranking) free(ranking);
11658     }
11659 }
11660
11661 /* Assumes program was just initialized (initString sent).
11662    Leaves program in force mode. */
11663 void
11664 FeedMovesToProgram (ChessProgramState *cps, int upto)
11665 {
11666     int i;
11667
11668     if (appData.debugMode)
11669       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11670               startedFromSetupPosition ? "position and " : "",
11671               backwardMostMove, upto, cps->which);
11672     if(currentlyInitializedVariant != gameInfo.variant) {
11673       char buf[MSG_SIZ];
11674         // [HGM] variantswitch: make engine aware of new variant
11675         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11676                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11677                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11678         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11679         SendToProgram(buf, cps);
11680         currentlyInitializedVariant = gameInfo.variant;
11681     }
11682     SendToProgram("force\n", cps);
11683     if (startedFromSetupPosition) {
11684         SendBoard(cps, backwardMostMove);
11685     if (appData.debugMode) {
11686         fprintf(debugFP, "feedMoves\n");
11687     }
11688     }
11689     for (i = backwardMostMove; i < upto; i++) {
11690         SendMoveToProgram(i, cps);
11691     }
11692 }
11693
11694
11695 int
11696 ResurrectChessProgram ()
11697 {
11698      /* The chess program may have exited.
11699         If so, restart it and feed it all the moves made so far. */
11700     static int doInit = 0;
11701
11702     if (appData.noChessProgram) return 1;
11703
11704     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11705         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11706         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11707         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11708     } else {
11709         if (first.pr != NoProc) return 1;
11710         StartChessProgram(&first);
11711     }
11712     InitChessProgram(&first, FALSE);
11713     FeedMovesToProgram(&first, currentMove);
11714
11715     if (!first.sendTime) {
11716         /* can't tell gnuchess what its clock should read,
11717            so we bow to its notion. */
11718         ResetClocks();
11719         timeRemaining[0][currentMove] = whiteTimeRemaining;
11720         timeRemaining[1][currentMove] = blackTimeRemaining;
11721     }
11722
11723     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11724                 appData.icsEngineAnalyze) && first.analysisSupport) {
11725       SendToProgram("analyze\n", &first);
11726       first.analyzing = TRUE;
11727     }
11728     return 1;
11729 }
11730
11731 /*
11732  * Button procedures
11733  */
11734 void
11735 Reset (int redraw, int init)
11736 {
11737     int i;
11738
11739     if (appData.debugMode) {
11740         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11741                 redraw, init, gameMode);
11742     }
11743     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11744     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11745     CleanupTail(); // [HGM] vari: delete any stored variations
11746     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11747     pausing = pauseExamInvalid = FALSE;
11748     startedFromSetupPosition = blackPlaysFirst = FALSE;
11749     firstMove = TRUE;
11750     whiteFlag = blackFlag = FALSE;
11751     userOfferedDraw = FALSE;
11752     hintRequested = bookRequested = FALSE;
11753     first.maybeThinking = FALSE;
11754     second.maybeThinking = FALSE;
11755     first.bookSuspend = FALSE; // [HGM] book
11756     second.bookSuspend = FALSE;
11757     thinkOutput[0] = NULLCHAR;
11758     lastHint[0] = NULLCHAR;
11759     ClearGameInfo(&gameInfo);
11760     gameInfo.variant = StringToVariant(appData.variant);
11761     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11762     ics_user_moved = ics_clock_paused = FALSE;
11763     ics_getting_history = H_FALSE;
11764     ics_gamenum = -1;
11765     white_holding[0] = black_holding[0] = NULLCHAR;
11766     ClearProgramStats();
11767     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11768
11769     ResetFrontEnd();
11770     ClearHighlights();
11771     flipView = appData.flipView;
11772     ClearPremoveHighlights();
11773     gotPremove = FALSE;
11774     alarmSounded = FALSE;
11775     killX = killY = -1; // [HGM] lion
11776
11777     GameEnds(EndOfFile, NULL, GE_PLAYER);
11778     if(appData.serverMovesName != NULL) {
11779         /* [HGM] prepare to make moves file for broadcasting */
11780         clock_t t = clock();
11781         if(serverMoves != NULL) fclose(serverMoves);
11782         serverMoves = fopen(appData.serverMovesName, "r");
11783         if(serverMoves != NULL) {
11784             fclose(serverMoves);
11785             /* delay 15 sec before overwriting, so all clients can see end */
11786             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11787         }
11788         serverMoves = fopen(appData.serverMovesName, "w");
11789     }
11790
11791     ExitAnalyzeMode();
11792     gameMode = BeginningOfGame;
11793     ModeHighlight();
11794     if(appData.icsActive) gameInfo.variant = VariantNormal;
11795     currentMove = forwardMostMove = backwardMostMove = 0;
11796     MarkTargetSquares(1);
11797     InitPosition(redraw);
11798     for (i = 0; i < MAX_MOVES; i++) {
11799         if (commentList[i] != NULL) {
11800             free(commentList[i]);
11801             commentList[i] = NULL;
11802         }
11803     }
11804     ResetClocks();
11805     timeRemaining[0][0] = whiteTimeRemaining;
11806     timeRemaining[1][0] = blackTimeRemaining;
11807
11808     if (first.pr == NoProc) {
11809         StartChessProgram(&first);
11810     }
11811     if (init) {
11812             InitChessProgram(&first, startedFromSetupPosition);
11813     }
11814     DisplayTitle("");
11815     DisplayMessage("", "");
11816     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11817     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11818     ClearMap();        // [HGM] exclude: invalidate map
11819 }
11820
11821 void
11822 AutoPlayGameLoop ()
11823 {
11824     for (;;) {
11825         if (!AutoPlayOneMove())
11826           return;
11827         if (matchMode || appData.timeDelay == 0)
11828           continue;
11829         if (appData.timeDelay < 0)
11830           return;
11831         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11832         break;
11833     }
11834 }
11835
11836 void
11837 AnalyzeNextGame()
11838 {
11839     ReloadGame(1); // next game
11840 }
11841
11842 int
11843 AutoPlayOneMove ()
11844 {
11845     int fromX, fromY, toX, toY;
11846
11847     if (appData.debugMode) {
11848       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11849     }
11850
11851     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11852       return FALSE;
11853
11854     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11855       pvInfoList[currentMove].depth = programStats.depth;
11856       pvInfoList[currentMove].score = programStats.score;
11857       pvInfoList[currentMove].time  = 0;
11858       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11859       else { // append analysis of final position as comment
11860         char buf[MSG_SIZ];
11861         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11862         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11863       }
11864       programStats.depth = 0;
11865     }
11866
11867     if (currentMove >= forwardMostMove) {
11868       if(gameMode == AnalyzeFile) {
11869           if(appData.loadGameIndex == -1) {
11870             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11871           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11872           } else {
11873           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11874         }
11875       }
11876 //      gameMode = EndOfGame;
11877 //      ModeHighlight();
11878
11879       /* [AS] Clear current move marker at the end of a game */
11880       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11881
11882       return FALSE;
11883     }
11884
11885     toX = moveList[currentMove][2] - AAA;
11886     toY = moveList[currentMove][3] - ONE;
11887
11888     if (moveList[currentMove][1] == '@') {
11889         if (appData.highlightLastMove) {
11890             SetHighlights(-1, -1, toX, toY);
11891         }
11892     } else {
11893         int viaX = moveList[currentMove][5] - AAA;
11894         int viaY = moveList[currentMove][6] - ONE;
11895         fromX = moveList[currentMove][0] - AAA;
11896         fromY = moveList[currentMove][1] - ONE;
11897
11898         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11899
11900         if(moveList[currentMove][4] == ';') { // multi-leg
11901             ChessSquare piece = boards[currentMove][viaY][viaX];
11902             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11903             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11904             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11905             boards[currentMove][viaY][viaX] = piece;
11906         } else
11907         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11908
11909         if (appData.highlightLastMove) {
11910             SetHighlights(fromX, fromY, toX, toY);
11911         }
11912     }
11913     DisplayMove(currentMove);
11914     SendMoveToProgram(currentMove++, &first);
11915     DisplayBothClocks();
11916     DrawPosition(FALSE, boards[currentMove]);
11917     // [HGM] PV info: always display, routine tests if empty
11918     DisplayComment(currentMove - 1, commentList[currentMove]);
11919     return TRUE;
11920 }
11921
11922
11923 int
11924 LoadGameOneMove (ChessMove readAhead)
11925 {
11926     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11927     char promoChar = NULLCHAR;
11928     ChessMove moveType;
11929     char move[MSG_SIZ];
11930     char *p, *q;
11931
11932     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11933         gameMode != AnalyzeMode && gameMode != Training) {
11934         gameFileFP = NULL;
11935         return FALSE;
11936     }
11937
11938     yyboardindex = forwardMostMove;
11939     if (readAhead != EndOfFile) {
11940       moveType = readAhead;
11941     } else {
11942       if (gameFileFP == NULL)
11943           return FALSE;
11944       moveType = (ChessMove) Myylex();
11945     }
11946
11947     done = FALSE;
11948     switch (moveType) {
11949       case Comment:
11950         if (appData.debugMode)
11951           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11952         p = yy_text;
11953
11954         /* append the comment but don't display it */
11955         AppendComment(currentMove, p, FALSE);
11956         return TRUE;
11957
11958       case WhiteCapturesEnPassant:
11959       case BlackCapturesEnPassant:
11960       case WhitePromotion:
11961       case BlackPromotion:
11962       case WhiteNonPromotion:
11963       case BlackNonPromotion:
11964       case NormalMove:
11965       case FirstLeg:
11966       case WhiteKingSideCastle:
11967       case WhiteQueenSideCastle:
11968       case BlackKingSideCastle:
11969       case BlackQueenSideCastle:
11970       case WhiteKingSideCastleWild:
11971       case WhiteQueenSideCastleWild:
11972       case BlackKingSideCastleWild:
11973       case BlackQueenSideCastleWild:
11974       /* PUSH Fabien */
11975       case WhiteHSideCastleFR:
11976       case WhiteASideCastleFR:
11977       case BlackHSideCastleFR:
11978       case BlackASideCastleFR:
11979       /* POP Fabien */
11980         if (appData.debugMode)
11981           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11982         fromX = currentMoveString[0] - AAA;
11983         fromY = currentMoveString[1] - ONE;
11984         toX = currentMoveString[2] - AAA;
11985         toY = currentMoveString[3] - ONE;
11986         promoChar = currentMoveString[4];
11987         if(promoChar == ';') promoChar = NULLCHAR;
11988         break;
11989
11990       case WhiteDrop:
11991       case BlackDrop:
11992         if (appData.debugMode)
11993           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11994         fromX = moveType == WhiteDrop ?
11995           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11996         (int) CharToPiece(ToLower(currentMoveString[0]));
11997         fromY = DROP_RANK;
11998         toX = currentMoveString[2] - AAA;
11999         toY = currentMoveString[3] - ONE;
12000         break;
12001
12002       case WhiteWins:
12003       case BlackWins:
12004       case GameIsDrawn:
12005       case GameUnfinished:
12006         if (appData.debugMode)
12007           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12008         p = strchr(yy_text, '{');
12009         if (p == NULL) p = strchr(yy_text, '(');
12010         if (p == NULL) {
12011             p = yy_text;
12012             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12013         } else {
12014             q = strchr(p, *p == '{' ? '}' : ')');
12015             if (q != NULL) *q = NULLCHAR;
12016             p++;
12017         }
12018         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12019         GameEnds(moveType, p, GE_FILE);
12020         done = TRUE;
12021         if (cmailMsgLoaded) {
12022             ClearHighlights();
12023             flipView = WhiteOnMove(currentMove);
12024             if (moveType == GameUnfinished) flipView = !flipView;
12025             if (appData.debugMode)
12026               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12027         }
12028         break;
12029
12030       case EndOfFile:
12031         if (appData.debugMode)
12032           fprintf(debugFP, "Parser hit end of file\n");
12033         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12034           case MT_NONE:
12035           case MT_CHECK:
12036             break;
12037           case MT_CHECKMATE:
12038           case MT_STAINMATE:
12039             if (WhiteOnMove(currentMove)) {
12040                 GameEnds(BlackWins, "Black mates", GE_FILE);
12041             } else {
12042                 GameEnds(WhiteWins, "White mates", GE_FILE);
12043             }
12044             break;
12045           case MT_STALEMATE:
12046             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12047             break;
12048         }
12049         done = TRUE;
12050         break;
12051
12052       case MoveNumberOne:
12053         if (lastLoadGameStart == GNUChessGame) {
12054             /* GNUChessGames have numbers, but they aren't move numbers */
12055             if (appData.debugMode)
12056               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12057                       yy_text, (int) moveType);
12058             return LoadGameOneMove(EndOfFile); /* tail recursion */
12059         }
12060         /* else fall thru */
12061
12062       case XBoardGame:
12063       case GNUChessGame:
12064       case PGNTag:
12065         /* Reached start of next game in file */
12066         if (appData.debugMode)
12067           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12068         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12069           case MT_NONE:
12070           case MT_CHECK:
12071             break;
12072           case MT_CHECKMATE:
12073           case MT_STAINMATE:
12074             if (WhiteOnMove(currentMove)) {
12075                 GameEnds(BlackWins, "Black mates", GE_FILE);
12076             } else {
12077                 GameEnds(WhiteWins, "White mates", GE_FILE);
12078             }
12079             break;
12080           case MT_STALEMATE:
12081             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12082             break;
12083         }
12084         done = TRUE;
12085         break;
12086
12087       case PositionDiagram:     /* should not happen; ignore */
12088       case ElapsedTime:         /* ignore */
12089       case NAG:                 /* ignore */
12090         if (appData.debugMode)
12091           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12092                   yy_text, (int) moveType);
12093         return LoadGameOneMove(EndOfFile); /* tail recursion */
12094
12095       case IllegalMove:
12096         if (appData.testLegality) {
12097             if (appData.debugMode)
12098               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12099             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12100                     (forwardMostMove / 2) + 1,
12101                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12102             DisplayError(move, 0);
12103             done = TRUE;
12104         } else {
12105             if (appData.debugMode)
12106               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12107                       yy_text, currentMoveString);
12108             if(currentMoveString[1] == '@') {
12109                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12110                 fromY = DROP_RANK;
12111             } else {
12112                 fromX = currentMoveString[0] - AAA;
12113                 fromY = currentMoveString[1] - ONE;
12114             }
12115             toX = currentMoveString[2] - AAA;
12116             toY = currentMoveString[3] - ONE;
12117             promoChar = currentMoveString[4];
12118         }
12119         break;
12120
12121       case AmbiguousMove:
12122         if (appData.debugMode)
12123           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12124         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12125                 (forwardMostMove / 2) + 1,
12126                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12127         DisplayError(move, 0);
12128         done = TRUE;
12129         break;
12130
12131       default:
12132       case ImpossibleMove:
12133         if (appData.debugMode)
12134           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12135         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12136                 (forwardMostMove / 2) + 1,
12137                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12138         DisplayError(move, 0);
12139         done = TRUE;
12140         break;
12141     }
12142
12143     if (done) {
12144         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12145             DrawPosition(FALSE, boards[currentMove]);
12146             DisplayBothClocks();
12147             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12148               DisplayComment(currentMove - 1, commentList[currentMove]);
12149         }
12150         (void) StopLoadGameTimer();
12151         gameFileFP = NULL;
12152         cmailOldMove = forwardMostMove;
12153         return FALSE;
12154     } else {
12155         /* currentMoveString is set as a side-effect of yylex */
12156
12157         thinkOutput[0] = NULLCHAR;
12158         MakeMove(fromX, fromY, toX, toY, promoChar);
12159         killX = killY = -1; // [HGM] lion: used up
12160         currentMove = forwardMostMove;
12161         return TRUE;
12162     }
12163 }
12164
12165 /* Load the nth game from the given file */
12166 int
12167 LoadGameFromFile (char *filename, int n, char *title, int useList)
12168 {
12169     FILE *f;
12170     char buf[MSG_SIZ];
12171
12172     if (strcmp(filename, "-") == 0) {
12173         f = stdin;
12174         title = "stdin";
12175     } else {
12176         f = fopen(filename, "rb");
12177         if (f == NULL) {
12178           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12179             DisplayError(buf, errno);
12180             return FALSE;
12181         }
12182     }
12183     if (fseek(f, 0, 0) == -1) {
12184         /* f is not seekable; probably a pipe */
12185         useList = FALSE;
12186     }
12187     if (useList && n == 0) {
12188         int error = GameListBuild(f);
12189         if (error) {
12190             DisplayError(_("Cannot build game list"), error);
12191         } else if (!ListEmpty(&gameList) &&
12192                    ((ListGame *) gameList.tailPred)->number > 1) {
12193             GameListPopUp(f, title);
12194             return TRUE;
12195         }
12196         GameListDestroy();
12197         n = 1;
12198     }
12199     if (n == 0) n = 1;
12200     return LoadGame(f, n, title, FALSE);
12201 }
12202
12203
12204 void
12205 MakeRegisteredMove ()
12206 {
12207     int fromX, fromY, toX, toY;
12208     char promoChar;
12209     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12210         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12211           case CMAIL_MOVE:
12212           case CMAIL_DRAW:
12213             if (appData.debugMode)
12214               fprintf(debugFP, "Restoring %s for game %d\n",
12215                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12216
12217             thinkOutput[0] = NULLCHAR;
12218             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12219             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12220             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12221             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12222             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12223             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12224             MakeMove(fromX, fromY, toX, toY, promoChar);
12225             ShowMove(fromX, fromY, toX, toY);
12226
12227             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12228               case MT_NONE:
12229               case MT_CHECK:
12230                 break;
12231
12232               case MT_CHECKMATE:
12233               case MT_STAINMATE:
12234                 if (WhiteOnMove(currentMove)) {
12235                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12236                 } else {
12237                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12238                 }
12239                 break;
12240
12241               case MT_STALEMATE:
12242                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12243                 break;
12244             }
12245
12246             break;
12247
12248           case CMAIL_RESIGN:
12249             if (WhiteOnMove(currentMove)) {
12250                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12251             } else {
12252                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12253             }
12254             break;
12255
12256           case CMAIL_ACCEPT:
12257             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12258             break;
12259
12260           default:
12261             break;
12262         }
12263     }
12264
12265     return;
12266 }
12267
12268 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12269 int
12270 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12271 {
12272     int retVal;
12273
12274     if (gameNumber > nCmailGames) {
12275         DisplayError(_("No more games in this message"), 0);
12276         return FALSE;
12277     }
12278     if (f == lastLoadGameFP) {
12279         int offset = gameNumber - lastLoadGameNumber;
12280         if (offset == 0) {
12281             cmailMsg[0] = NULLCHAR;
12282             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12283                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12284                 nCmailMovesRegistered--;
12285             }
12286             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12287             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12288                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12289             }
12290         } else {
12291             if (! RegisterMove()) return FALSE;
12292         }
12293     }
12294
12295     retVal = LoadGame(f, gameNumber, title, useList);
12296
12297     /* Make move registered during previous look at this game, if any */
12298     MakeRegisteredMove();
12299
12300     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12301         commentList[currentMove]
12302           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12303         DisplayComment(currentMove - 1, commentList[currentMove]);
12304     }
12305
12306     return retVal;
12307 }
12308
12309 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12310 int
12311 ReloadGame (int offset)
12312 {
12313     int gameNumber = lastLoadGameNumber + offset;
12314     if (lastLoadGameFP == NULL) {
12315         DisplayError(_("No game has been loaded yet"), 0);
12316         return FALSE;
12317     }
12318     if (gameNumber <= 0) {
12319         DisplayError(_("Can't back up any further"), 0);
12320         return FALSE;
12321     }
12322     if (cmailMsgLoaded) {
12323         return CmailLoadGame(lastLoadGameFP, gameNumber,
12324                              lastLoadGameTitle, lastLoadGameUseList);
12325     } else {
12326         return LoadGame(lastLoadGameFP, gameNumber,
12327                         lastLoadGameTitle, lastLoadGameUseList);
12328     }
12329 }
12330
12331 int keys[EmptySquare+1];
12332
12333 int
12334 PositionMatches (Board b1, Board b2)
12335 {
12336     int r, f, sum=0;
12337     switch(appData.searchMode) {
12338         case 1: return CompareWithRights(b1, b2);
12339         case 2:
12340             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12341                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12342             }
12343             return TRUE;
12344         case 3:
12345             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12346               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12347                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12348             }
12349             return sum==0;
12350         case 4:
12351             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12352                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12353             }
12354             return sum==0;
12355     }
12356     return TRUE;
12357 }
12358
12359 #define Q_PROMO  4
12360 #define Q_EP     3
12361 #define Q_BCASTL 2
12362 #define Q_WCASTL 1
12363
12364 int pieceList[256], quickBoard[256];
12365 ChessSquare pieceType[256] = { EmptySquare };
12366 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12367 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12368 int soughtTotal, turn;
12369 Boolean epOK, flipSearch;
12370
12371 typedef struct {
12372     unsigned char piece, to;
12373 } Move;
12374
12375 #define DSIZE (250000)
12376
12377 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12378 Move *moveDatabase = initialSpace;
12379 unsigned int movePtr, dataSize = DSIZE;
12380
12381 int
12382 MakePieceList (Board board, int *counts)
12383 {
12384     int r, f, n=Q_PROMO, total=0;
12385     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12386     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12387         int sq = f + (r<<4);
12388         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12389             quickBoard[sq] = ++n;
12390             pieceList[n] = sq;
12391             pieceType[n] = board[r][f];
12392             counts[board[r][f]]++;
12393             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12394             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12395             total++;
12396         }
12397     }
12398     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12399     return total;
12400 }
12401
12402 void
12403 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12404 {
12405     int sq = fromX + (fromY<<4);
12406     int piece = quickBoard[sq], rook;
12407     quickBoard[sq] = 0;
12408     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12409     if(piece == pieceList[1] && fromY == toY) {
12410       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12411         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12412         moveDatabase[movePtr++].piece = Q_WCASTL;
12413         quickBoard[sq] = piece;
12414         piece = quickBoard[from]; quickBoard[from] = 0;
12415         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12416       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12417         quickBoard[sq] = 0; // remove Rook
12418         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12419         moveDatabase[movePtr++].piece = Q_WCASTL;
12420         quickBoard[sq] = pieceList[1]; // put King
12421         piece = rook;
12422         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12423       }
12424     } else
12425     if(piece == pieceList[2] && fromY == toY) {
12426       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12427         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12428         moveDatabase[movePtr++].piece = Q_BCASTL;
12429         quickBoard[sq] = piece;
12430         piece = quickBoard[from]; quickBoard[from] = 0;
12431         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12432       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12433         quickBoard[sq] = 0; // remove Rook
12434         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12435         moveDatabase[movePtr++].piece = Q_BCASTL;
12436         quickBoard[sq] = pieceList[2]; // put King
12437         piece = rook;
12438         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12439       }
12440     } else
12441     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12442         quickBoard[(fromY<<4)+toX] = 0;
12443         moveDatabase[movePtr].piece = Q_EP;
12444         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12445         moveDatabase[movePtr].to = sq;
12446     } else
12447     if(promoPiece != pieceType[piece]) {
12448         moveDatabase[movePtr++].piece = Q_PROMO;
12449         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12450     }
12451     moveDatabase[movePtr].piece = piece;
12452     quickBoard[sq] = piece;
12453     movePtr++;
12454 }
12455
12456 int
12457 PackGame (Board board)
12458 {
12459     Move *newSpace = NULL;
12460     moveDatabase[movePtr].piece = 0; // terminate previous game
12461     if(movePtr > dataSize) {
12462         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12463         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12464         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12465         if(newSpace) {
12466             int i;
12467             Move *p = moveDatabase, *q = newSpace;
12468             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12469             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12470             moveDatabase = newSpace;
12471         } else { // calloc failed, we must be out of memory. Too bad...
12472             dataSize = 0; // prevent calloc events for all subsequent games
12473             return 0;     // and signal this one isn't cached
12474         }
12475     }
12476     movePtr++;
12477     MakePieceList(board, counts);
12478     return movePtr;
12479 }
12480
12481 int
12482 QuickCompare (Board board, int *minCounts, int *maxCounts)
12483 {   // compare according to search mode
12484     int r, f;
12485     switch(appData.searchMode)
12486     {
12487       case 1: // exact position match
12488         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12489         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12490             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12491         }
12492         break;
12493       case 2: // can have extra material on empty squares
12494         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12495             if(board[r][f] == EmptySquare) continue;
12496             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12497         }
12498         break;
12499       case 3: // material with exact Pawn structure
12500         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12501             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12502             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12503         } // fall through to material comparison
12504       case 4: // exact material
12505         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12506         break;
12507       case 6: // material range with given imbalance
12508         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12509         // fall through to range comparison
12510       case 5: // material range
12511         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12512     }
12513     return TRUE;
12514 }
12515
12516 int
12517 QuickScan (Board board, Move *move)
12518 {   // reconstruct game,and compare all positions in it
12519     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12520     do {
12521         int piece = move->piece;
12522         int to = move->to, from = pieceList[piece];
12523         if(found < 0) { // if already found just scan to game end for final piece count
12524           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12525            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12526            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12527                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12528             ) {
12529             static int lastCounts[EmptySquare+1];
12530             int i;
12531             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12532             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12533           } else stretch = 0;
12534           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12535           if(found >= 0 && !appData.minPieces) return found;
12536         }
12537         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12538           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12539           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12540             piece = (++move)->piece;
12541             from = pieceList[piece];
12542             counts[pieceType[piece]]--;
12543             pieceType[piece] = (ChessSquare) move->to;
12544             counts[move->to]++;
12545           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12546             counts[pieceType[quickBoard[to]]]--;
12547             quickBoard[to] = 0; total--;
12548             move++;
12549             continue;
12550           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12551             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12552             from  = pieceList[piece]; // so this must be King
12553             quickBoard[from] = 0;
12554             pieceList[piece] = to;
12555             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12556             quickBoard[from] = 0; // rook
12557             quickBoard[to] = piece;
12558             to = move->to; piece = move->piece;
12559             goto aftercastle;
12560           }
12561         }
12562         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12563         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12564         quickBoard[from] = 0;
12565       aftercastle:
12566         quickBoard[to] = piece;
12567         pieceList[piece] = to;
12568         cnt++; turn ^= 3;
12569         move++;
12570     } while(1);
12571 }
12572
12573 void
12574 InitSearch ()
12575 {
12576     int r, f;
12577     flipSearch = FALSE;
12578     CopyBoard(soughtBoard, boards[currentMove]);
12579     soughtTotal = MakePieceList(soughtBoard, maxSought);
12580     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12581     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12582     CopyBoard(reverseBoard, boards[currentMove]);
12583     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12584         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12585         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12586         reverseBoard[r][f] = piece;
12587     }
12588     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12589     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12590     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12591                  || (boards[currentMove][CASTLING][2] == NoRights ||
12592                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12593                  && (boards[currentMove][CASTLING][5] == NoRights ||
12594                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12595       ) {
12596         flipSearch = TRUE;
12597         CopyBoard(flipBoard, soughtBoard);
12598         CopyBoard(rotateBoard, reverseBoard);
12599         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12600             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12601             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12602         }
12603     }
12604     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12605     if(appData.searchMode >= 5) {
12606         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12607         MakePieceList(soughtBoard, minSought);
12608         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12609     }
12610     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12611         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12612 }
12613
12614 GameInfo dummyInfo;
12615 static int creatingBook;
12616
12617 int
12618 GameContainsPosition (FILE *f, ListGame *lg)
12619 {
12620     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12621     int fromX, fromY, toX, toY;
12622     char promoChar;
12623     static int initDone=FALSE;
12624
12625     // weed out games based on numerical tag comparison
12626     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12627     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12628     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12629     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12630     if(!initDone) {
12631         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12632         initDone = TRUE;
12633     }
12634     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12635     else CopyBoard(boards[scratch], initialPosition); // default start position
12636     if(lg->moves) {
12637         turn = btm + 1;
12638         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12639         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12640     }
12641     if(btm) plyNr++;
12642     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12643     fseek(f, lg->offset, 0);
12644     yynewfile(f);
12645     while(1) {
12646         yyboardindex = scratch;
12647         quickFlag = plyNr+1;
12648         next = Myylex();
12649         quickFlag = 0;
12650         switch(next) {
12651             case PGNTag:
12652                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12653             default:
12654                 continue;
12655
12656             case XBoardGame:
12657             case GNUChessGame:
12658                 if(plyNr) return -1; // after we have seen moves, this is for new game
12659               continue;
12660
12661             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12662             case ImpossibleMove:
12663             case WhiteWins: // game ends here with these four
12664             case BlackWins:
12665             case GameIsDrawn:
12666             case GameUnfinished:
12667                 return -1;
12668
12669             case IllegalMove:
12670                 if(appData.testLegality) return -1;
12671             case WhiteCapturesEnPassant:
12672             case BlackCapturesEnPassant:
12673             case WhitePromotion:
12674             case BlackPromotion:
12675             case WhiteNonPromotion:
12676             case BlackNonPromotion:
12677             case NormalMove:
12678             case FirstLeg:
12679             case WhiteKingSideCastle:
12680             case WhiteQueenSideCastle:
12681             case BlackKingSideCastle:
12682             case BlackQueenSideCastle:
12683             case WhiteKingSideCastleWild:
12684             case WhiteQueenSideCastleWild:
12685             case BlackKingSideCastleWild:
12686             case BlackQueenSideCastleWild:
12687             case WhiteHSideCastleFR:
12688             case WhiteASideCastleFR:
12689             case BlackHSideCastleFR:
12690             case BlackASideCastleFR:
12691                 fromX = currentMoveString[0] - AAA;
12692                 fromY = currentMoveString[1] - ONE;
12693                 toX = currentMoveString[2] - AAA;
12694                 toY = currentMoveString[3] - ONE;
12695                 promoChar = currentMoveString[4];
12696                 break;
12697             case WhiteDrop:
12698             case BlackDrop:
12699                 fromX = next == WhiteDrop ?
12700                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12701                   (int) CharToPiece(ToLower(currentMoveString[0]));
12702                 fromY = DROP_RANK;
12703                 toX = currentMoveString[2] - AAA;
12704                 toY = currentMoveString[3] - ONE;
12705                 promoChar = 0;
12706                 break;
12707         }
12708         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12709         plyNr++;
12710         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12711         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12712         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12713         if(appData.findMirror) {
12714             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12715             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12716         }
12717     }
12718 }
12719
12720 /* Load the nth game from open file f */
12721 int
12722 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12723 {
12724     ChessMove cm;
12725     char buf[MSG_SIZ];
12726     int gn = gameNumber;
12727     ListGame *lg = NULL;
12728     int numPGNTags = 0;
12729     int err, pos = -1;
12730     GameMode oldGameMode;
12731     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12732     char oldName[MSG_SIZ];
12733
12734     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12735
12736     if (appData.debugMode)
12737         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12738
12739     if (gameMode == Training )
12740         SetTrainingModeOff();
12741
12742     oldGameMode = gameMode;
12743     if (gameMode != BeginningOfGame) {
12744       Reset(FALSE, TRUE);
12745     }
12746     killX = killY = -1; // [HGM] lion: in case we did not Reset
12747
12748     gameFileFP = f;
12749     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12750         fclose(lastLoadGameFP);
12751     }
12752
12753     if (useList) {
12754         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12755
12756         if (lg) {
12757             fseek(f, lg->offset, 0);
12758             GameListHighlight(gameNumber);
12759             pos = lg->position;
12760             gn = 1;
12761         }
12762         else {
12763             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12764               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12765             else
12766             DisplayError(_("Game number out of range"), 0);
12767             return FALSE;
12768         }
12769     } else {
12770         GameListDestroy();
12771         if (fseek(f, 0, 0) == -1) {
12772             if (f == lastLoadGameFP ?
12773                 gameNumber == lastLoadGameNumber + 1 :
12774                 gameNumber == 1) {
12775                 gn = 1;
12776             } else {
12777                 DisplayError(_("Can't seek on game file"), 0);
12778                 return FALSE;
12779             }
12780         }
12781     }
12782     lastLoadGameFP = f;
12783     lastLoadGameNumber = gameNumber;
12784     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12785     lastLoadGameUseList = useList;
12786
12787     yynewfile(f);
12788
12789     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12790       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12791                 lg->gameInfo.black);
12792             DisplayTitle(buf);
12793     } else if (*title != NULLCHAR) {
12794         if (gameNumber > 1) {
12795           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12796             DisplayTitle(buf);
12797         } else {
12798             DisplayTitle(title);
12799         }
12800     }
12801
12802     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12803         gameMode = PlayFromGameFile;
12804         ModeHighlight();
12805     }
12806
12807     currentMove = forwardMostMove = backwardMostMove = 0;
12808     CopyBoard(boards[0], initialPosition);
12809     StopClocks();
12810
12811     /*
12812      * Skip the first gn-1 games in the file.
12813      * Also skip over anything that precedes an identifiable
12814      * start of game marker, to avoid being confused by
12815      * garbage at the start of the file.  Currently
12816      * recognized start of game markers are the move number "1",
12817      * the pattern "gnuchess .* game", the pattern
12818      * "^[#;%] [^ ]* game file", and a PGN tag block.
12819      * A game that starts with one of the latter two patterns
12820      * will also have a move number 1, possibly
12821      * following a position diagram.
12822      * 5-4-02: Let's try being more lenient and allowing a game to
12823      * start with an unnumbered move.  Does that break anything?
12824      */
12825     cm = lastLoadGameStart = EndOfFile;
12826     while (gn > 0) {
12827         yyboardindex = forwardMostMove;
12828         cm = (ChessMove) Myylex();
12829         switch (cm) {
12830           case EndOfFile:
12831             if (cmailMsgLoaded) {
12832                 nCmailGames = CMAIL_MAX_GAMES - gn;
12833             } else {
12834                 Reset(TRUE, TRUE);
12835                 DisplayError(_("Game not found in file"), 0);
12836             }
12837             return FALSE;
12838
12839           case GNUChessGame:
12840           case XBoardGame:
12841             gn--;
12842             lastLoadGameStart = cm;
12843             break;
12844
12845           case MoveNumberOne:
12846             switch (lastLoadGameStart) {
12847               case GNUChessGame:
12848               case XBoardGame:
12849               case PGNTag:
12850                 break;
12851               case MoveNumberOne:
12852               case EndOfFile:
12853                 gn--;           /* count this game */
12854                 lastLoadGameStart = cm;
12855                 break;
12856               default:
12857                 /* impossible */
12858                 break;
12859             }
12860             break;
12861
12862           case PGNTag:
12863             switch (lastLoadGameStart) {
12864               case GNUChessGame:
12865               case PGNTag:
12866               case MoveNumberOne:
12867               case EndOfFile:
12868                 gn--;           /* count this game */
12869                 lastLoadGameStart = cm;
12870                 break;
12871               case XBoardGame:
12872                 lastLoadGameStart = cm; /* game counted already */
12873                 break;
12874               default:
12875                 /* impossible */
12876                 break;
12877             }
12878             if (gn > 0) {
12879                 do {
12880                     yyboardindex = forwardMostMove;
12881                     cm = (ChessMove) Myylex();
12882                 } while (cm == PGNTag || cm == Comment);
12883             }
12884             break;
12885
12886           case WhiteWins:
12887           case BlackWins:
12888           case GameIsDrawn:
12889             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12890                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12891                     != CMAIL_OLD_RESULT) {
12892                     nCmailResults ++ ;
12893                     cmailResult[  CMAIL_MAX_GAMES
12894                                 - gn - 1] = CMAIL_OLD_RESULT;
12895                 }
12896             }
12897             break;
12898
12899           case NormalMove:
12900           case FirstLeg:
12901             /* Only a NormalMove can be at the start of a game
12902              * without a position diagram. */
12903             if (lastLoadGameStart == EndOfFile ) {
12904               gn--;
12905               lastLoadGameStart = MoveNumberOne;
12906             }
12907             break;
12908
12909           default:
12910             break;
12911         }
12912     }
12913
12914     if (appData.debugMode)
12915       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12916
12917     if (cm == XBoardGame) {
12918         /* Skip any header junk before position diagram and/or move 1 */
12919         for (;;) {
12920             yyboardindex = forwardMostMove;
12921             cm = (ChessMove) Myylex();
12922
12923             if (cm == EndOfFile ||
12924                 cm == GNUChessGame || cm == XBoardGame) {
12925                 /* Empty game; pretend end-of-file and handle later */
12926                 cm = EndOfFile;
12927                 break;
12928             }
12929
12930             if (cm == MoveNumberOne || cm == PositionDiagram ||
12931                 cm == PGNTag || cm == Comment)
12932               break;
12933         }
12934     } else if (cm == GNUChessGame) {
12935         if (gameInfo.event != NULL) {
12936             free(gameInfo.event);
12937         }
12938         gameInfo.event = StrSave(yy_text);
12939     }
12940
12941     startedFromSetupPosition = FALSE;
12942     while (cm == PGNTag) {
12943         if (appData.debugMode)
12944           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12945         err = ParsePGNTag(yy_text, &gameInfo);
12946         if (!err) numPGNTags++;
12947
12948         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12949         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
12950             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12951             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12952             InitPosition(TRUE);
12953             oldVariant = gameInfo.variant;
12954             if (appData.debugMode)
12955               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12956         }
12957
12958
12959         if (gameInfo.fen != NULL) {
12960           Board initial_position;
12961           startedFromSetupPosition = TRUE;
12962           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12963             Reset(TRUE, TRUE);
12964             DisplayError(_("Bad FEN position in file"), 0);
12965             return FALSE;
12966           }
12967           CopyBoard(boards[0], initial_position);
12968           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
12969             CopyBoard(initialPosition, initial_position);
12970           if (blackPlaysFirst) {
12971             currentMove = forwardMostMove = backwardMostMove = 1;
12972             CopyBoard(boards[1], initial_position);
12973             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12974             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12975             timeRemaining[0][1] = whiteTimeRemaining;
12976             timeRemaining[1][1] = blackTimeRemaining;
12977             if (commentList[0] != NULL) {
12978               commentList[1] = commentList[0];
12979               commentList[0] = NULL;
12980             }
12981           } else {
12982             currentMove = forwardMostMove = backwardMostMove = 0;
12983           }
12984           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12985           {   int i;
12986               initialRulePlies = FENrulePlies;
12987               for( i=0; i< nrCastlingRights; i++ )
12988                   initialRights[i] = initial_position[CASTLING][i];
12989           }
12990           yyboardindex = forwardMostMove;
12991           free(gameInfo.fen);
12992           gameInfo.fen = NULL;
12993         }
12994
12995         yyboardindex = forwardMostMove;
12996         cm = (ChessMove) Myylex();
12997
12998         /* Handle comments interspersed among the tags */
12999         while (cm == Comment) {
13000             char *p;
13001             if (appData.debugMode)
13002               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13003             p = yy_text;
13004             AppendComment(currentMove, p, FALSE);
13005             yyboardindex = forwardMostMove;
13006             cm = (ChessMove) Myylex();
13007         }
13008     }
13009
13010     /* don't rely on existence of Event tag since if game was
13011      * pasted from clipboard the Event tag may not exist
13012      */
13013     if (numPGNTags > 0){
13014         char *tags;
13015         if (gameInfo.variant == VariantNormal) {
13016           VariantClass v = StringToVariant(gameInfo.event);
13017           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13018           if(v < VariantShogi) gameInfo.variant = v;
13019         }
13020         if (!matchMode) {
13021           if( appData.autoDisplayTags ) {
13022             tags = PGNTags(&gameInfo);
13023             TagsPopUp(tags, CmailMsg());
13024             free(tags);
13025           }
13026         }
13027     } else {
13028         /* Make something up, but don't display it now */
13029         SetGameInfo();
13030         TagsPopDown();
13031     }
13032
13033     if (cm == PositionDiagram) {
13034         int i, j;
13035         char *p;
13036         Board initial_position;
13037
13038         if (appData.debugMode)
13039           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13040
13041         if (!startedFromSetupPosition) {
13042             p = yy_text;
13043             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13044               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13045                 switch (*p) {
13046                   case '{':
13047                   case '[':
13048                   case '-':
13049                   case ' ':
13050                   case '\t':
13051                   case '\n':
13052                   case '\r':
13053                     break;
13054                   default:
13055                     initial_position[i][j++] = CharToPiece(*p);
13056                     break;
13057                 }
13058             while (*p == ' ' || *p == '\t' ||
13059                    *p == '\n' || *p == '\r') p++;
13060
13061             if (strncmp(p, "black", strlen("black"))==0)
13062               blackPlaysFirst = TRUE;
13063             else
13064               blackPlaysFirst = FALSE;
13065             startedFromSetupPosition = TRUE;
13066
13067             CopyBoard(boards[0], initial_position);
13068             if (blackPlaysFirst) {
13069                 currentMove = forwardMostMove = backwardMostMove = 1;
13070                 CopyBoard(boards[1], initial_position);
13071                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13072                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13073                 timeRemaining[0][1] = whiteTimeRemaining;
13074                 timeRemaining[1][1] = blackTimeRemaining;
13075                 if (commentList[0] != NULL) {
13076                     commentList[1] = commentList[0];
13077                     commentList[0] = NULL;
13078                 }
13079             } else {
13080                 currentMove = forwardMostMove = backwardMostMove = 0;
13081             }
13082         }
13083         yyboardindex = forwardMostMove;
13084         cm = (ChessMove) Myylex();
13085     }
13086
13087   if(!creatingBook) {
13088     if (first.pr == NoProc) {
13089         StartChessProgram(&first);
13090     }
13091     InitChessProgram(&first, FALSE);
13092     if(gameInfo.variant == VariantUnknown && *oldName) {
13093         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13094         gameInfo.variant = v;
13095     }
13096     SendToProgram("force\n", &first);
13097     if (startedFromSetupPosition) {
13098         SendBoard(&first, forwardMostMove);
13099     if (appData.debugMode) {
13100         fprintf(debugFP, "Load Game\n");
13101     }
13102         DisplayBothClocks();
13103     }
13104   }
13105
13106     /* [HGM] server: flag to write setup moves in broadcast file as one */
13107     loadFlag = appData.suppressLoadMoves;
13108
13109     while (cm == Comment) {
13110         char *p;
13111         if (appData.debugMode)
13112           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13113         p = yy_text;
13114         AppendComment(currentMove, p, FALSE);
13115         yyboardindex = forwardMostMove;
13116         cm = (ChessMove) Myylex();
13117     }
13118
13119     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13120         cm == WhiteWins || cm == BlackWins ||
13121         cm == GameIsDrawn || cm == GameUnfinished) {
13122         DisplayMessage("", _("No moves in game"));
13123         if (cmailMsgLoaded) {
13124             if (appData.debugMode)
13125               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13126             ClearHighlights();
13127             flipView = FALSE;
13128         }
13129         DrawPosition(FALSE, boards[currentMove]);
13130         DisplayBothClocks();
13131         gameMode = EditGame;
13132         ModeHighlight();
13133         gameFileFP = NULL;
13134         cmailOldMove = 0;
13135         return TRUE;
13136     }
13137
13138     // [HGM] PV info: routine tests if comment empty
13139     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13140         DisplayComment(currentMove - 1, commentList[currentMove]);
13141     }
13142     if (!matchMode && appData.timeDelay != 0)
13143       DrawPosition(FALSE, boards[currentMove]);
13144
13145     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13146       programStats.ok_to_send = 1;
13147     }
13148
13149     /* if the first token after the PGN tags is a move
13150      * and not move number 1, retrieve it from the parser
13151      */
13152     if (cm != MoveNumberOne)
13153         LoadGameOneMove(cm);
13154
13155     /* load the remaining moves from the file */
13156     while (LoadGameOneMove(EndOfFile)) {
13157       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13158       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13159     }
13160
13161     /* rewind to the start of the game */
13162     currentMove = backwardMostMove;
13163
13164     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13165
13166     if (oldGameMode == AnalyzeFile) {
13167       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13168       AnalyzeFileEvent();
13169     } else
13170     if (oldGameMode == AnalyzeMode) {
13171       AnalyzeFileEvent();
13172     }
13173
13174     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13175         long int w, b; // [HGM] adjourn: restore saved clock times
13176         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13177         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13178             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13179             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13180         }
13181     }
13182
13183     if(creatingBook) return TRUE;
13184     if (!matchMode && pos > 0) {
13185         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13186     } else
13187     if (matchMode || appData.timeDelay == 0) {
13188       ToEndEvent();
13189     } else if (appData.timeDelay > 0) {
13190       AutoPlayGameLoop();
13191     }
13192
13193     if (appData.debugMode)
13194         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13195
13196     loadFlag = 0; /* [HGM] true game starts */
13197     return TRUE;
13198 }
13199
13200 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13201 int
13202 ReloadPosition (int offset)
13203 {
13204     int positionNumber = lastLoadPositionNumber + offset;
13205     if (lastLoadPositionFP == NULL) {
13206         DisplayError(_("No position has been loaded yet"), 0);
13207         return FALSE;
13208     }
13209     if (positionNumber <= 0) {
13210         DisplayError(_("Can't back up any further"), 0);
13211         return FALSE;
13212     }
13213     return LoadPosition(lastLoadPositionFP, positionNumber,
13214                         lastLoadPositionTitle);
13215 }
13216
13217 /* Load the nth position from the given file */
13218 int
13219 LoadPositionFromFile (char *filename, int n, char *title)
13220 {
13221     FILE *f;
13222     char buf[MSG_SIZ];
13223
13224     if (strcmp(filename, "-") == 0) {
13225         return LoadPosition(stdin, n, "stdin");
13226     } else {
13227         f = fopen(filename, "rb");
13228         if (f == NULL) {
13229             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13230             DisplayError(buf, errno);
13231             return FALSE;
13232         } else {
13233             return LoadPosition(f, n, title);
13234         }
13235     }
13236 }
13237
13238 /* Load the nth position from the given open file, and close it */
13239 int
13240 LoadPosition (FILE *f, int positionNumber, char *title)
13241 {
13242     char *p, line[MSG_SIZ];
13243     Board initial_position;
13244     int i, j, fenMode, pn;
13245
13246     if (gameMode == Training )
13247         SetTrainingModeOff();
13248
13249     if (gameMode != BeginningOfGame) {
13250         Reset(FALSE, TRUE);
13251     }
13252     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13253         fclose(lastLoadPositionFP);
13254     }
13255     if (positionNumber == 0) positionNumber = 1;
13256     lastLoadPositionFP = f;
13257     lastLoadPositionNumber = positionNumber;
13258     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13259     if (first.pr == NoProc && !appData.noChessProgram) {
13260       StartChessProgram(&first);
13261       InitChessProgram(&first, FALSE);
13262     }
13263     pn = positionNumber;
13264     if (positionNumber < 0) {
13265         /* Negative position number means to seek to that byte offset */
13266         if (fseek(f, -positionNumber, 0) == -1) {
13267             DisplayError(_("Can't seek on position file"), 0);
13268             return FALSE;
13269         };
13270         pn = 1;
13271     } else {
13272         if (fseek(f, 0, 0) == -1) {
13273             if (f == lastLoadPositionFP ?
13274                 positionNumber == lastLoadPositionNumber + 1 :
13275                 positionNumber == 1) {
13276                 pn = 1;
13277             } else {
13278                 DisplayError(_("Can't seek on position file"), 0);
13279                 return FALSE;
13280             }
13281         }
13282     }
13283     /* See if this file is FEN or old-style xboard */
13284     if (fgets(line, MSG_SIZ, f) == NULL) {
13285         DisplayError(_("Position not found in file"), 0);
13286         return FALSE;
13287     }
13288     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13289     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13290
13291     if (pn >= 2) {
13292         if (fenMode || line[0] == '#') pn--;
13293         while (pn > 0) {
13294             /* skip positions before number pn */
13295             if (fgets(line, MSG_SIZ, f) == NULL) {
13296                 Reset(TRUE, TRUE);
13297                 DisplayError(_("Position not found in file"), 0);
13298                 return FALSE;
13299             }
13300             if (fenMode || line[0] == '#') pn--;
13301         }
13302     }
13303
13304     if (fenMode) {
13305         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13306             DisplayError(_("Bad FEN position in file"), 0);
13307             return FALSE;
13308         }
13309     } else {
13310         (void) fgets(line, MSG_SIZ, f);
13311         (void) fgets(line, MSG_SIZ, f);
13312
13313         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13314             (void) fgets(line, MSG_SIZ, f);
13315             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13316                 if (*p == ' ')
13317                   continue;
13318                 initial_position[i][j++] = CharToPiece(*p);
13319             }
13320         }
13321
13322         blackPlaysFirst = FALSE;
13323         if (!feof(f)) {
13324             (void) fgets(line, MSG_SIZ, f);
13325             if (strncmp(line, "black", strlen("black"))==0)
13326               blackPlaysFirst = TRUE;
13327         }
13328     }
13329     startedFromSetupPosition = TRUE;
13330
13331     CopyBoard(boards[0], initial_position);
13332     if (blackPlaysFirst) {
13333         currentMove = forwardMostMove = backwardMostMove = 1;
13334         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13335         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13336         CopyBoard(boards[1], initial_position);
13337         DisplayMessage("", _("Black to play"));
13338     } else {
13339         currentMove = forwardMostMove = backwardMostMove = 0;
13340         DisplayMessage("", _("White to play"));
13341     }
13342     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13343     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13344         SendToProgram("force\n", &first);
13345         SendBoard(&first, forwardMostMove);
13346     }
13347     if (appData.debugMode) {
13348 int i, j;
13349   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13350   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13351         fprintf(debugFP, "Load Position\n");
13352     }
13353
13354     if (positionNumber > 1) {
13355       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13356         DisplayTitle(line);
13357     } else {
13358         DisplayTitle(title);
13359     }
13360     gameMode = EditGame;
13361     ModeHighlight();
13362     ResetClocks();
13363     timeRemaining[0][1] = whiteTimeRemaining;
13364     timeRemaining[1][1] = blackTimeRemaining;
13365     DrawPosition(FALSE, boards[currentMove]);
13366
13367     return TRUE;
13368 }
13369
13370
13371 void
13372 CopyPlayerNameIntoFileName (char **dest, char *src)
13373 {
13374     while (*src != NULLCHAR && *src != ',') {
13375         if (*src == ' ') {
13376             *(*dest)++ = '_';
13377             src++;
13378         } else {
13379             *(*dest)++ = *src++;
13380         }
13381     }
13382 }
13383
13384 char *
13385 DefaultFileName (char *ext)
13386 {
13387     static char def[MSG_SIZ];
13388     char *p;
13389
13390     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13391         p = def;
13392         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13393         *p++ = '-';
13394         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13395         *p++ = '.';
13396         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13397     } else {
13398         def[0] = NULLCHAR;
13399     }
13400     return def;
13401 }
13402
13403 /* Save the current game to the given file */
13404 int
13405 SaveGameToFile (char *filename, int append)
13406 {
13407     FILE *f;
13408     char buf[MSG_SIZ];
13409     int result, i, t,tot=0;
13410
13411     if (strcmp(filename, "-") == 0) {
13412         return SaveGame(stdout, 0, NULL);
13413     } else {
13414         for(i=0; i<10; i++) { // upto 10 tries
13415              f = fopen(filename, append ? "a" : "w");
13416              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13417              if(f || errno != 13) break;
13418              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13419              tot += t;
13420         }
13421         if (f == NULL) {
13422             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13423             DisplayError(buf, errno);
13424             return FALSE;
13425         } else {
13426             safeStrCpy(buf, lastMsg, MSG_SIZ);
13427             DisplayMessage(_("Waiting for access to save file"), "");
13428             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13429             DisplayMessage(_("Saving game"), "");
13430             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13431             result = SaveGame(f, 0, NULL);
13432             DisplayMessage(buf, "");
13433             return result;
13434         }
13435     }
13436 }
13437
13438 char *
13439 SavePart (char *str)
13440 {
13441     static char buf[MSG_SIZ];
13442     char *p;
13443
13444     p = strchr(str, ' ');
13445     if (p == NULL) return str;
13446     strncpy(buf, str, p - str);
13447     buf[p - str] = NULLCHAR;
13448     return buf;
13449 }
13450
13451 #define PGN_MAX_LINE 75
13452
13453 #define PGN_SIDE_WHITE  0
13454 #define PGN_SIDE_BLACK  1
13455
13456 static int
13457 FindFirstMoveOutOfBook (int side)
13458 {
13459     int result = -1;
13460
13461     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13462         int index = backwardMostMove;
13463         int has_book_hit = 0;
13464
13465         if( (index % 2) != side ) {
13466             index++;
13467         }
13468
13469         while( index < forwardMostMove ) {
13470             /* Check to see if engine is in book */
13471             int depth = pvInfoList[index].depth;
13472             int score = pvInfoList[index].score;
13473             int in_book = 0;
13474
13475             if( depth <= 2 ) {
13476                 in_book = 1;
13477             }
13478             else if( score == 0 && depth == 63 ) {
13479                 in_book = 1; /* Zappa */
13480             }
13481             else if( score == 2 && depth == 99 ) {
13482                 in_book = 1; /* Abrok */
13483             }
13484
13485             has_book_hit += in_book;
13486
13487             if( ! in_book ) {
13488                 result = index;
13489
13490                 break;
13491             }
13492
13493             index += 2;
13494         }
13495     }
13496
13497     return result;
13498 }
13499
13500 void
13501 GetOutOfBookInfo (char * buf)
13502 {
13503     int oob[2];
13504     int i;
13505     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13506
13507     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13508     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13509
13510     *buf = '\0';
13511
13512     if( oob[0] >= 0 || oob[1] >= 0 ) {
13513         for( i=0; i<2; i++ ) {
13514             int idx = oob[i];
13515
13516             if( idx >= 0 ) {
13517                 if( i > 0 && oob[0] >= 0 ) {
13518                     strcat( buf, "   " );
13519                 }
13520
13521                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13522                 sprintf( buf+strlen(buf), "%s%.2f",
13523                     pvInfoList[idx].score >= 0 ? "+" : "",
13524                     pvInfoList[idx].score / 100.0 );
13525             }
13526         }
13527     }
13528 }
13529
13530 /* Save game in PGN style */
13531 static void
13532 SaveGamePGN2 (FILE *f)
13533 {
13534     int i, offset, linelen, newblock;
13535 //    char *movetext;
13536     char numtext[32];
13537     int movelen, numlen, blank;
13538     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13539
13540     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13541
13542     PrintPGNTags(f, &gameInfo);
13543
13544     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13545
13546     if (backwardMostMove > 0 || startedFromSetupPosition) {
13547         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13548         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13549         fprintf(f, "\n{--------------\n");
13550         PrintPosition(f, backwardMostMove);
13551         fprintf(f, "--------------}\n");
13552         free(fen);
13553     }
13554     else {
13555         /* [AS] Out of book annotation */
13556         if( appData.saveOutOfBookInfo ) {
13557             char buf[64];
13558
13559             GetOutOfBookInfo( buf );
13560
13561             if( buf[0] != '\0' ) {
13562                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13563             }
13564         }
13565
13566         fprintf(f, "\n");
13567     }
13568
13569     i = backwardMostMove;
13570     linelen = 0;
13571     newblock = TRUE;
13572
13573     while (i < forwardMostMove) {
13574         /* Print comments preceding this move */
13575         if (commentList[i] != NULL) {
13576             if (linelen > 0) fprintf(f, "\n");
13577             fprintf(f, "%s", commentList[i]);
13578             linelen = 0;
13579             newblock = TRUE;
13580         }
13581
13582         /* Format move number */
13583         if ((i % 2) == 0)
13584           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13585         else
13586           if (newblock)
13587             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13588           else
13589             numtext[0] = NULLCHAR;
13590
13591         numlen = strlen(numtext);
13592         newblock = FALSE;
13593
13594         /* Print move number */
13595         blank = linelen > 0 && numlen > 0;
13596         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13597             fprintf(f, "\n");
13598             linelen = 0;
13599             blank = 0;
13600         }
13601         if (blank) {
13602             fprintf(f, " ");
13603             linelen++;
13604         }
13605         fprintf(f, "%s", numtext);
13606         linelen += numlen;
13607
13608         /* Get move */
13609         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13610         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13611
13612         /* Print move */
13613         blank = linelen > 0 && movelen > 0;
13614         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13615             fprintf(f, "\n");
13616             linelen = 0;
13617             blank = 0;
13618         }
13619         if (blank) {
13620             fprintf(f, " ");
13621             linelen++;
13622         }
13623         fprintf(f, "%s", move_buffer);
13624         linelen += movelen;
13625
13626         /* [AS] Add PV info if present */
13627         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13628             /* [HGM] add time */
13629             char buf[MSG_SIZ]; int seconds;
13630
13631             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13632
13633             if( seconds <= 0)
13634               buf[0] = 0;
13635             else
13636               if( seconds < 30 )
13637                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13638               else
13639                 {
13640                   seconds = (seconds + 4)/10; // round to full seconds
13641                   if( seconds < 60 )
13642                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13643                   else
13644                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13645                 }
13646
13647             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13648                       pvInfoList[i].score >= 0 ? "+" : "",
13649                       pvInfoList[i].score / 100.0,
13650                       pvInfoList[i].depth,
13651                       buf );
13652
13653             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13654
13655             /* Print score/depth */
13656             blank = linelen > 0 && movelen > 0;
13657             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13658                 fprintf(f, "\n");
13659                 linelen = 0;
13660                 blank = 0;
13661             }
13662             if (blank) {
13663                 fprintf(f, " ");
13664                 linelen++;
13665             }
13666             fprintf(f, "%s", move_buffer);
13667             linelen += movelen;
13668         }
13669
13670         i++;
13671     }
13672
13673     /* Start a new line */
13674     if (linelen > 0) fprintf(f, "\n");
13675
13676     /* Print comments after last move */
13677     if (commentList[i] != NULL) {
13678         fprintf(f, "%s\n", commentList[i]);
13679     }
13680
13681     /* Print result */
13682     if (gameInfo.resultDetails != NULL &&
13683         gameInfo.resultDetails[0] != NULLCHAR) {
13684         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13685         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13686            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13687             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13688         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13689     } else {
13690         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13691     }
13692 }
13693
13694 /* Save game in PGN style and close the file */
13695 int
13696 SaveGamePGN (FILE *f)
13697 {
13698     SaveGamePGN2(f);
13699     fclose(f);
13700     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13701     return TRUE;
13702 }
13703
13704 /* Save game in old style and close the file */
13705 int
13706 SaveGameOldStyle (FILE *f)
13707 {
13708     int i, offset;
13709     time_t tm;
13710
13711     tm = time((time_t *) NULL);
13712
13713     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13714     PrintOpponents(f);
13715
13716     if (backwardMostMove > 0 || startedFromSetupPosition) {
13717         fprintf(f, "\n[--------------\n");
13718         PrintPosition(f, backwardMostMove);
13719         fprintf(f, "--------------]\n");
13720     } else {
13721         fprintf(f, "\n");
13722     }
13723
13724     i = backwardMostMove;
13725     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13726
13727     while (i < forwardMostMove) {
13728         if (commentList[i] != NULL) {
13729             fprintf(f, "[%s]\n", commentList[i]);
13730         }
13731
13732         if ((i % 2) == 1) {
13733             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13734             i++;
13735         } else {
13736             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13737             i++;
13738             if (commentList[i] != NULL) {
13739                 fprintf(f, "\n");
13740                 continue;
13741             }
13742             if (i >= forwardMostMove) {
13743                 fprintf(f, "\n");
13744                 break;
13745             }
13746             fprintf(f, "%s\n", parseList[i]);
13747             i++;
13748         }
13749     }
13750
13751     if (commentList[i] != NULL) {
13752         fprintf(f, "[%s]\n", commentList[i]);
13753     }
13754
13755     /* This isn't really the old style, but it's close enough */
13756     if (gameInfo.resultDetails != NULL &&
13757         gameInfo.resultDetails[0] != NULLCHAR) {
13758         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13759                 gameInfo.resultDetails);
13760     } else {
13761         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13762     }
13763
13764     fclose(f);
13765     return TRUE;
13766 }
13767
13768 /* Save the current game to open file f and close the file */
13769 int
13770 SaveGame (FILE *f, int dummy, char *dummy2)
13771 {
13772     if (gameMode == EditPosition) EditPositionDone(TRUE);
13773     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13774     if (appData.oldSaveStyle)
13775       return SaveGameOldStyle(f);
13776     else
13777       return SaveGamePGN(f);
13778 }
13779
13780 /* Save the current position to the given file */
13781 int
13782 SavePositionToFile (char *filename)
13783 {
13784     FILE *f;
13785     char buf[MSG_SIZ];
13786
13787     if (strcmp(filename, "-") == 0) {
13788         return SavePosition(stdout, 0, NULL);
13789     } else {
13790         f = fopen(filename, "a");
13791         if (f == NULL) {
13792             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13793             DisplayError(buf, errno);
13794             return FALSE;
13795         } else {
13796             safeStrCpy(buf, lastMsg, MSG_SIZ);
13797             DisplayMessage(_("Waiting for access to save file"), "");
13798             flock(fileno(f), LOCK_EX); // [HGM] lock
13799             DisplayMessage(_("Saving position"), "");
13800             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13801             SavePosition(f, 0, NULL);
13802             DisplayMessage(buf, "");
13803             return TRUE;
13804         }
13805     }
13806 }
13807
13808 /* Save the current position to the given open file and close the file */
13809 int
13810 SavePosition (FILE *f, int dummy, char *dummy2)
13811 {
13812     time_t tm;
13813     char *fen;
13814
13815     if (gameMode == EditPosition) EditPositionDone(TRUE);
13816     if (appData.oldSaveStyle) {
13817         tm = time((time_t *) NULL);
13818
13819         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13820         PrintOpponents(f);
13821         fprintf(f, "[--------------\n");
13822         PrintPosition(f, currentMove);
13823         fprintf(f, "--------------]\n");
13824     } else {
13825         fen = PositionToFEN(currentMove, NULL, 1);
13826         fprintf(f, "%s\n", fen);
13827         free(fen);
13828     }
13829     fclose(f);
13830     return TRUE;
13831 }
13832
13833 void
13834 ReloadCmailMsgEvent (int unregister)
13835 {
13836 #if !WIN32
13837     static char *inFilename = NULL;
13838     static char *outFilename;
13839     int i;
13840     struct stat inbuf, outbuf;
13841     int status;
13842
13843     /* Any registered moves are unregistered if unregister is set, */
13844     /* i.e. invoked by the signal handler */
13845     if (unregister) {
13846         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13847             cmailMoveRegistered[i] = FALSE;
13848             if (cmailCommentList[i] != NULL) {
13849                 free(cmailCommentList[i]);
13850                 cmailCommentList[i] = NULL;
13851             }
13852         }
13853         nCmailMovesRegistered = 0;
13854     }
13855
13856     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13857         cmailResult[i] = CMAIL_NOT_RESULT;
13858     }
13859     nCmailResults = 0;
13860
13861     if (inFilename == NULL) {
13862         /* Because the filenames are static they only get malloced once  */
13863         /* and they never get freed                                      */
13864         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13865         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13866
13867         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13868         sprintf(outFilename, "%s.out", appData.cmailGameName);
13869     }
13870
13871     status = stat(outFilename, &outbuf);
13872     if (status < 0) {
13873         cmailMailedMove = FALSE;
13874     } else {
13875         status = stat(inFilename, &inbuf);
13876         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13877     }
13878
13879     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13880        counts the games, notes how each one terminated, etc.
13881
13882        It would be nice to remove this kludge and instead gather all
13883        the information while building the game list.  (And to keep it
13884        in the game list nodes instead of having a bunch of fixed-size
13885        parallel arrays.)  Note this will require getting each game's
13886        termination from the PGN tags, as the game list builder does
13887        not process the game moves.  --mann
13888        */
13889     cmailMsgLoaded = TRUE;
13890     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13891
13892     /* Load first game in the file or popup game menu */
13893     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13894
13895 #endif /* !WIN32 */
13896     return;
13897 }
13898
13899 int
13900 RegisterMove ()
13901 {
13902     FILE *f;
13903     char string[MSG_SIZ];
13904
13905     if (   cmailMailedMove
13906         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13907         return TRUE;            /* Allow free viewing  */
13908     }
13909
13910     /* Unregister move to ensure that we don't leave RegisterMove        */
13911     /* with the move registered when the conditions for registering no   */
13912     /* longer hold                                                       */
13913     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13914         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13915         nCmailMovesRegistered --;
13916
13917         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13918           {
13919               free(cmailCommentList[lastLoadGameNumber - 1]);
13920               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13921           }
13922     }
13923
13924     if (cmailOldMove == -1) {
13925         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13926         return FALSE;
13927     }
13928
13929     if (currentMove > cmailOldMove + 1) {
13930         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13931         return FALSE;
13932     }
13933
13934     if (currentMove < cmailOldMove) {
13935         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13936         return FALSE;
13937     }
13938
13939     if (forwardMostMove > currentMove) {
13940         /* Silently truncate extra moves */
13941         TruncateGame();
13942     }
13943
13944     if (   (currentMove == cmailOldMove + 1)
13945         || (   (currentMove == cmailOldMove)
13946             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13947                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13948         if (gameInfo.result != GameUnfinished) {
13949             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13950         }
13951
13952         if (commentList[currentMove] != NULL) {
13953             cmailCommentList[lastLoadGameNumber - 1]
13954               = StrSave(commentList[currentMove]);
13955         }
13956         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13957
13958         if (appData.debugMode)
13959           fprintf(debugFP, "Saving %s for game %d\n",
13960                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13961
13962         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13963
13964         f = fopen(string, "w");
13965         if (appData.oldSaveStyle) {
13966             SaveGameOldStyle(f); /* also closes the file */
13967
13968             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13969             f = fopen(string, "w");
13970             SavePosition(f, 0, NULL); /* also closes the file */
13971         } else {
13972             fprintf(f, "{--------------\n");
13973             PrintPosition(f, currentMove);
13974             fprintf(f, "--------------}\n\n");
13975
13976             SaveGame(f, 0, NULL); /* also closes the file*/
13977         }
13978
13979         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13980         nCmailMovesRegistered ++;
13981     } else if (nCmailGames == 1) {
13982         DisplayError(_("You have not made a move yet"), 0);
13983         return FALSE;
13984     }
13985
13986     return TRUE;
13987 }
13988
13989 void
13990 MailMoveEvent ()
13991 {
13992 #if !WIN32
13993     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13994     FILE *commandOutput;
13995     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13996     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13997     int nBuffers;
13998     int i;
13999     int archived;
14000     char *arcDir;
14001
14002     if (! cmailMsgLoaded) {
14003         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14004         return;
14005     }
14006
14007     if (nCmailGames == nCmailResults) {
14008         DisplayError(_("No unfinished games"), 0);
14009         return;
14010     }
14011
14012 #if CMAIL_PROHIBIT_REMAIL
14013     if (cmailMailedMove) {
14014       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);
14015         DisplayError(msg, 0);
14016         return;
14017     }
14018 #endif
14019
14020     if (! (cmailMailedMove || RegisterMove())) return;
14021
14022     if (   cmailMailedMove
14023         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14024       snprintf(string, MSG_SIZ, partCommandString,
14025                appData.debugMode ? " -v" : "", appData.cmailGameName);
14026         commandOutput = popen(string, "r");
14027
14028         if (commandOutput == NULL) {
14029             DisplayError(_("Failed to invoke cmail"), 0);
14030         } else {
14031             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14032                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14033             }
14034             if (nBuffers > 1) {
14035                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14036                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14037                 nBytes = MSG_SIZ - 1;
14038             } else {
14039                 (void) memcpy(msg, buffer, nBytes);
14040             }
14041             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14042
14043             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14044                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14045
14046                 archived = TRUE;
14047                 for (i = 0; i < nCmailGames; i ++) {
14048                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14049                         archived = FALSE;
14050                     }
14051                 }
14052                 if (   archived
14053                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14054                         != NULL)) {
14055                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14056                            arcDir,
14057                            appData.cmailGameName,
14058                            gameInfo.date);
14059                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14060                     cmailMsgLoaded = FALSE;
14061                 }
14062             }
14063
14064             DisplayInformation(msg);
14065             pclose(commandOutput);
14066         }
14067     } else {
14068         if ((*cmailMsg) != '\0') {
14069             DisplayInformation(cmailMsg);
14070         }
14071     }
14072
14073     return;
14074 #endif /* !WIN32 */
14075 }
14076
14077 char *
14078 CmailMsg ()
14079 {
14080 #if WIN32
14081     return NULL;
14082 #else
14083     int  prependComma = 0;
14084     char number[5];
14085     char string[MSG_SIZ];       /* Space for game-list */
14086     int  i;
14087
14088     if (!cmailMsgLoaded) return "";
14089
14090     if (cmailMailedMove) {
14091       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14092     } else {
14093         /* Create a list of games left */
14094       snprintf(string, MSG_SIZ, "[");
14095         for (i = 0; i < nCmailGames; i ++) {
14096             if (! (   cmailMoveRegistered[i]
14097                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14098                 if (prependComma) {
14099                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14100                 } else {
14101                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14102                     prependComma = 1;
14103                 }
14104
14105                 strcat(string, number);
14106             }
14107         }
14108         strcat(string, "]");
14109
14110         if (nCmailMovesRegistered + nCmailResults == 0) {
14111             switch (nCmailGames) {
14112               case 1:
14113                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14114                 break;
14115
14116               case 2:
14117                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14118                 break;
14119
14120               default:
14121                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14122                          nCmailGames);
14123                 break;
14124             }
14125         } else {
14126             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14127               case 1:
14128                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14129                          string);
14130                 break;
14131
14132               case 0:
14133                 if (nCmailResults == nCmailGames) {
14134                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14135                 } else {
14136                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14137                 }
14138                 break;
14139
14140               default:
14141                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14142                          string);
14143             }
14144         }
14145     }
14146     return cmailMsg;
14147 #endif /* WIN32 */
14148 }
14149
14150 void
14151 ResetGameEvent ()
14152 {
14153     if (gameMode == Training)
14154       SetTrainingModeOff();
14155
14156     Reset(TRUE, TRUE);
14157     cmailMsgLoaded = FALSE;
14158     if (appData.icsActive) {
14159       SendToICS(ics_prefix);
14160       SendToICS("refresh\n");
14161     }
14162 }
14163
14164 void
14165 ExitEvent (int status)
14166 {
14167     exiting++;
14168     if (exiting > 2) {
14169       /* Give up on clean exit */
14170       exit(status);
14171     }
14172     if (exiting > 1) {
14173       /* Keep trying for clean exit */
14174       return;
14175     }
14176
14177     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14178     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14179
14180     if (telnetISR != NULL) {
14181       RemoveInputSource(telnetISR);
14182     }
14183     if (icsPR != NoProc) {
14184       DestroyChildProcess(icsPR, TRUE);
14185     }
14186
14187     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14188     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14189
14190     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14191     /* make sure this other one finishes before killing it!                  */
14192     if(endingGame) { int count = 0;
14193         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14194         while(endingGame && count++ < 10) DoSleep(1);
14195         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14196     }
14197
14198     /* Kill off chess programs */
14199     if (first.pr != NoProc) {
14200         ExitAnalyzeMode();
14201
14202         DoSleep( appData.delayBeforeQuit );
14203         SendToProgram("quit\n", &first);
14204         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14205     }
14206     if (second.pr != NoProc) {
14207         DoSleep( appData.delayBeforeQuit );
14208         SendToProgram("quit\n", &second);
14209         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14210     }
14211     if (first.isr != NULL) {
14212         RemoveInputSource(first.isr);
14213     }
14214     if (second.isr != NULL) {
14215         RemoveInputSource(second.isr);
14216     }
14217
14218     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14219     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14220
14221     ShutDownFrontEnd();
14222     exit(status);
14223 }
14224
14225 void
14226 PauseEngine (ChessProgramState *cps)
14227 {
14228     SendToProgram("pause\n", cps);
14229     cps->pause = 2;
14230 }
14231
14232 void
14233 UnPauseEngine (ChessProgramState *cps)
14234 {
14235     SendToProgram("resume\n", cps);
14236     cps->pause = 1;
14237 }
14238
14239 void
14240 PauseEvent ()
14241 {
14242     if (appData.debugMode)
14243         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14244     if (pausing) {
14245         pausing = FALSE;
14246         ModeHighlight();
14247         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14248             StartClocks();
14249             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14250                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14251                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14252             }
14253             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14254             HandleMachineMove(stashedInputMove, stalledEngine);
14255             stalledEngine = NULL;
14256             return;
14257         }
14258         if (gameMode == MachinePlaysWhite ||
14259             gameMode == TwoMachinesPlay   ||
14260             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14261             if(first.pause)  UnPauseEngine(&first);
14262             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14263             if(second.pause) UnPauseEngine(&second);
14264             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14265             StartClocks();
14266         } else {
14267             DisplayBothClocks();
14268         }
14269         if (gameMode == PlayFromGameFile) {
14270             if (appData.timeDelay >= 0)
14271                 AutoPlayGameLoop();
14272         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14273             Reset(FALSE, TRUE);
14274             SendToICS(ics_prefix);
14275             SendToICS("refresh\n");
14276         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14277             ForwardInner(forwardMostMove);
14278         }
14279         pauseExamInvalid = FALSE;
14280     } else {
14281         switch (gameMode) {
14282           default:
14283             return;
14284           case IcsExamining:
14285             pauseExamForwardMostMove = forwardMostMove;
14286             pauseExamInvalid = FALSE;
14287             /* fall through */
14288           case IcsObserving:
14289           case IcsPlayingWhite:
14290           case IcsPlayingBlack:
14291             pausing = TRUE;
14292             ModeHighlight();
14293             return;
14294           case PlayFromGameFile:
14295             (void) StopLoadGameTimer();
14296             pausing = TRUE;
14297             ModeHighlight();
14298             break;
14299           case BeginningOfGame:
14300             if (appData.icsActive) return;
14301             /* else fall through */
14302           case MachinePlaysWhite:
14303           case MachinePlaysBlack:
14304           case TwoMachinesPlay:
14305             if (forwardMostMove == 0)
14306               return;           /* don't pause if no one has moved */
14307             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14308                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14309                 if(onMove->pause) {           // thinking engine can be paused
14310                     PauseEngine(onMove);      // do it
14311                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14312                         PauseEngine(onMove->other);
14313                     else
14314                         SendToProgram("easy\n", onMove->other);
14315                     StopClocks();
14316                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14317             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14318                 if(first.pause) {
14319                     PauseEngine(&first);
14320                     StopClocks();
14321                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14322             } else { // human on move, pause pondering by either method
14323                 if(first.pause)
14324                     PauseEngine(&first);
14325                 else if(appData.ponderNextMove)
14326                     SendToProgram("easy\n", &first);
14327                 StopClocks();
14328             }
14329             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14330           case AnalyzeMode:
14331             pausing = TRUE;
14332             ModeHighlight();
14333             break;
14334         }
14335     }
14336 }
14337
14338 void
14339 EditCommentEvent ()
14340 {
14341     char title[MSG_SIZ];
14342
14343     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14344       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14345     } else {
14346       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14347                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14348                parseList[currentMove - 1]);
14349     }
14350
14351     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14352 }
14353
14354
14355 void
14356 EditTagsEvent ()
14357 {
14358     char *tags = PGNTags(&gameInfo);
14359     bookUp = FALSE;
14360     EditTagsPopUp(tags, NULL);
14361     free(tags);
14362 }
14363
14364 void
14365 ToggleSecond ()
14366 {
14367   if(second.analyzing) {
14368     SendToProgram("exit\n", &second);
14369     second.analyzing = FALSE;
14370   } else {
14371     if (second.pr == NoProc) StartChessProgram(&second);
14372     InitChessProgram(&second, FALSE);
14373     FeedMovesToProgram(&second, currentMove);
14374
14375     SendToProgram("analyze\n", &second);
14376     second.analyzing = TRUE;
14377   }
14378 }
14379
14380 /* Toggle ShowThinking */
14381 void
14382 ToggleShowThinking()
14383 {
14384   appData.showThinking = !appData.showThinking;
14385   ShowThinkingEvent();
14386 }
14387
14388 int
14389 AnalyzeModeEvent ()
14390 {
14391     char buf[MSG_SIZ];
14392
14393     if (!first.analysisSupport) {
14394       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14395       DisplayError(buf, 0);
14396       return 0;
14397     }
14398     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14399     if (appData.icsActive) {
14400         if (gameMode != IcsObserving) {
14401           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14402             DisplayError(buf, 0);
14403             /* secure check */
14404             if (appData.icsEngineAnalyze) {
14405                 if (appData.debugMode)
14406                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14407                 ExitAnalyzeMode();
14408                 ModeHighlight();
14409             }
14410             return 0;
14411         }
14412         /* if enable, user wants to disable icsEngineAnalyze */
14413         if (appData.icsEngineAnalyze) {
14414                 ExitAnalyzeMode();
14415                 ModeHighlight();
14416                 return 0;
14417         }
14418         appData.icsEngineAnalyze = TRUE;
14419         if (appData.debugMode)
14420             fprintf(debugFP, "ICS engine analyze starting... \n");
14421     }
14422
14423     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14424     if (appData.noChessProgram || gameMode == AnalyzeMode)
14425       return 0;
14426
14427     if (gameMode != AnalyzeFile) {
14428         if (!appData.icsEngineAnalyze) {
14429                EditGameEvent();
14430                if (gameMode != EditGame) return 0;
14431         }
14432         if (!appData.showThinking) ToggleShowThinking();
14433         ResurrectChessProgram();
14434         SendToProgram("analyze\n", &first);
14435         first.analyzing = TRUE;
14436         /*first.maybeThinking = TRUE;*/
14437         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14438         EngineOutputPopUp();
14439     }
14440     if (!appData.icsEngineAnalyze) {
14441         gameMode = AnalyzeMode;
14442         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14443     }
14444     pausing = FALSE;
14445     ModeHighlight();
14446     SetGameInfo();
14447
14448     StartAnalysisClock();
14449     GetTimeMark(&lastNodeCountTime);
14450     lastNodeCount = 0;
14451     return 1;
14452 }
14453
14454 void
14455 AnalyzeFileEvent ()
14456 {
14457     if (appData.noChessProgram || gameMode == AnalyzeFile)
14458       return;
14459
14460     if (!first.analysisSupport) {
14461       char buf[MSG_SIZ];
14462       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14463       DisplayError(buf, 0);
14464       return;
14465     }
14466
14467     if (gameMode != AnalyzeMode) {
14468         keepInfo = 1; // mere annotating should not alter PGN tags
14469         EditGameEvent();
14470         keepInfo = 0;
14471         if (gameMode != EditGame) return;
14472         if (!appData.showThinking) ToggleShowThinking();
14473         ResurrectChessProgram();
14474         SendToProgram("analyze\n", &first);
14475         first.analyzing = TRUE;
14476         /*first.maybeThinking = TRUE;*/
14477         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14478         EngineOutputPopUp();
14479     }
14480     gameMode = AnalyzeFile;
14481     pausing = FALSE;
14482     ModeHighlight();
14483
14484     StartAnalysisClock();
14485     GetTimeMark(&lastNodeCountTime);
14486     lastNodeCount = 0;
14487     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14488     AnalysisPeriodicEvent(1);
14489 }
14490
14491 void
14492 MachineWhiteEvent ()
14493 {
14494     char buf[MSG_SIZ];
14495     char *bookHit = NULL;
14496
14497     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14498       return;
14499
14500
14501     if (gameMode == PlayFromGameFile ||
14502         gameMode == TwoMachinesPlay  ||
14503         gameMode == Training         ||
14504         gameMode == AnalyzeMode      ||
14505         gameMode == EndOfGame)
14506         EditGameEvent();
14507
14508     if (gameMode == EditPosition)
14509         EditPositionDone(TRUE);
14510
14511     if (!WhiteOnMove(currentMove)) {
14512         DisplayError(_("It is not White's turn"), 0);
14513         return;
14514     }
14515
14516     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14517       ExitAnalyzeMode();
14518
14519     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14520         gameMode == AnalyzeFile)
14521         TruncateGame();
14522
14523     ResurrectChessProgram();    /* in case it isn't running */
14524     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14525         gameMode = MachinePlaysWhite;
14526         ResetClocks();
14527     } else
14528     gameMode = MachinePlaysWhite;
14529     pausing = FALSE;
14530     ModeHighlight();
14531     SetGameInfo();
14532     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14533     DisplayTitle(buf);
14534     if (first.sendName) {
14535       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14536       SendToProgram(buf, &first);
14537     }
14538     if (first.sendTime) {
14539       if (first.useColors) {
14540         SendToProgram("black\n", &first); /*gnu kludge*/
14541       }
14542       SendTimeRemaining(&first, TRUE);
14543     }
14544     if (first.useColors) {
14545       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14546     }
14547     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14548     SetMachineThinkingEnables();
14549     first.maybeThinking = TRUE;
14550     StartClocks();
14551     firstMove = FALSE;
14552
14553     if (appData.autoFlipView && !flipView) {
14554       flipView = !flipView;
14555       DrawPosition(FALSE, NULL);
14556       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14557     }
14558
14559     if(bookHit) { // [HGM] book: simulate book reply
14560         static char bookMove[MSG_SIZ]; // a bit generous?
14561
14562         programStats.nodes = programStats.depth = programStats.time =
14563         programStats.score = programStats.got_only_move = 0;
14564         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14565
14566         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14567         strcat(bookMove, bookHit);
14568         HandleMachineMove(bookMove, &first);
14569     }
14570 }
14571
14572 void
14573 MachineBlackEvent ()
14574 {
14575   char buf[MSG_SIZ];
14576   char *bookHit = NULL;
14577
14578     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14579         return;
14580
14581
14582     if (gameMode == PlayFromGameFile ||
14583         gameMode == TwoMachinesPlay  ||
14584         gameMode == Training         ||
14585         gameMode == AnalyzeMode      ||
14586         gameMode == EndOfGame)
14587         EditGameEvent();
14588
14589     if (gameMode == EditPosition)
14590         EditPositionDone(TRUE);
14591
14592     if (WhiteOnMove(currentMove)) {
14593         DisplayError(_("It is not Black's turn"), 0);
14594         return;
14595     }
14596
14597     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14598       ExitAnalyzeMode();
14599
14600     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14601         gameMode == AnalyzeFile)
14602         TruncateGame();
14603
14604     ResurrectChessProgram();    /* in case it isn't running */
14605     gameMode = MachinePlaysBlack;
14606     pausing = FALSE;
14607     ModeHighlight();
14608     SetGameInfo();
14609     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14610     DisplayTitle(buf);
14611     if (first.sendName) {
14612       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14613       SendToProgram(buf, &first);
14614     }
14615     if (first.sendTime) {
14616       if (first.useColors) {
14617         SendToProgram("white\n", &first); /*gnu kludge*/
14618       }
14619       SendTimeRemaining(&first, FALSE);
14620     }
14621     if (first.useColors) {
14622       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14623     }
14624     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14625     SetMachineThinkingEnables();
14626     first.maybeThinking = TRUE;
14627     StartClocks();
14628
14629     if (appData.autoFlipView && flipView) {
14630       flipView = !flipView;
14631       DrawPosition(FALSE, NULL);
14632       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14633     }
14634     if(bookHit) { // [HGM] book: simulate book reply
14635         static char bookMove[MSG_SIZ]; // a bit generous?
14636
14637         programStats.nodes = programStats.depth = programStats.time =
14638         programStats.score = programStats.got_only_move = 0;
14639         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14640
14641         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14642         strcat(bookMove, bookHit);
14643         HandleMachineMove(bookMove, &first);
14644     }
14645 }
14646
14647
14648 void
14649 DisplayTwoMachinesTitle ()
14650 {
14651     char buf[MSG_SIZ];
14652     if (appData.matchGames > 0) {
14653         if(appData.tourneyFile[0]) {
14654           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14655                    gameInfo.white, _("vs."), gameInfo.black,
14656                    nextGame+1, appData.matchGames+1,
14657                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14658         } else
14659         if (first.twoMachinesColor[0] == 'w') {
14660           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14661                    gameInfo.white, _("vs."),  gameInfo.black,
14662                    first.matchWins, second.matchWins,
14663                    matchGame - 1 - (first.matchWins + second.matchWins));
14664         } else {
14665           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14666                    gameInfo.white, _("vs."), gameInfo.black,
14667                    second.matchWins, first.matchWins,
14668                    matchGame - 1 - (first.matchWins + second.matchWins));
14669         }
14670     } else {
14671       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14672     }
14673     DisplayTitle(buf);
14674 }
14675
14676 void
14677 SettingsMenuIfReady ()
14678 {
14679   if (second.lastPing != second.lastPong) {
14680     DisplayMessage("", _("Waiting for second chess program"));
14681     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14682     return;
14683   }
14684   ThawUI();
14685   DisplayMessage("", "");
14686   SettingsPopUp(&second);
14687 }
14688
14689 int
14690 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14691 {
14692     char buf[MSG_SIZ];
14693     if (cps->pr == NoProc) {
14694         StartChessProgram(cps);
14695         if (cps->protocolVersion == 1) {
14696           retry();
14697           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14698         } else {
14699           /* kludge: allow timeout for initial "feature" command */
14700           if(retry != TwoMachinesEventIfReady) FreezeUI();
14701           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14702           DisplayMessage("", buf);
14703           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14704         }
14705         return 1;
14706     }
14707     return 0;
14708 }
14709
14710 void
14711 TwoMachinesEvent P((void))
14712 {
14713     int i;
14714     char buf[MSG_SIZ];
14715     ChessProgramState *onmove;
14716     char *bookHit = NULL;
14717     static int stalling = 0;
14718     TimeMark now;
14719     long wait;
14720
14721     if (appData.noChessProgram) return;
14722
14723     switch (gameMode) {
14724       case TwoMachinesPlay:
14725         return;
14726       case MachinePlaysWhite:
14727       case MachinePlaysBlack:
14728         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14729             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14730             return;
14731         }
14732         /* fall through */
14733       case BeginningOfGame:
14734       case PlayFromGameFile:
14735       case EndOfGame:
14736         EditGameEvent();
14737         if (gameMode != EditGame) return;
14738         break;
14739       case EditPosition:
14740         EditPositionDone(TRUE);
14741         break;
14742       case AnalyzeMode:
14743       case AnalyzeFile:
14744         ExitAnalyzeMode();
14745         break;
14746       case EditGame:
14747       default:
14748         break;
14749     }
14750
14751 //    forwardMostMove = currentMove;
14752     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14753     startingEngine = TRUE;
14754
14755     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14756
14757     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14758     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14759       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14760       return;
14761     }
14762     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14763
14764     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14765                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14766         startingEngine = matchMode = FALSE;
14767         DisplayError("second engine does not play this", 0);
14768         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14769         EditGameEvent(); // switch back to EditGame mode
14770         return;
14771     }
14772
14773     if(!stalling) {
14774       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14775       SendToProgram("force\n", &second);
14776       stalling = 1;
14777       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14778       return;
14779     }
14780     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14781     if(appData.matchPause>10000 || appData.matchPause<10)
14782                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14783     wait = SubtractTimeMarks(&now, &pauseStart);
14784     if(wait < appData.matchPause) {
14785         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14786         return;
14787     }
14788     // we are now committed to starting the game
14789     stalling = 0;
14790     DisplayMessage("", "");
14791     if (startedFromSetupPosition) {
14792         SendBoard(&second, backwardMostMove);
14793     if (appData.debugMode) {
14794         fprintf(debugFP, "Two Machines\n");
14795     }
14796     }
14797     for (i = backwardMostMove; i < forwardMostMove; i++) {
14798         SendMoveToProgram(i, &second);
14799     }
14800
14801     gameMode = TwoMachinesPlay;
14802     pausing = startingEngine = FALSE;
14803     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14804     SetGameInfo();
14805     DisplayTwoMachinesTitle();
14806     firstMove = TRUE;
14807     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14808         onmove = &first;
14809     } else {
14810         onmove = &second;
14811     }
14812     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14813     SendToProgram(first.computerString, &first);
14814     if (first.sendName) {
14815       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14816       SendToProgram(buf, &first);
14817     }
14818     SendToProgram(second.computerString, &second);
14819     if (second.sendName) {
14820       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14821       SendToProgram(buf, &second);
14822     }
14823
14824     ResetClocks();
14825     if (!first.sendTime || !second.sendTime) {
14826         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14827         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14828     }
14829     if (onmove->sendTime) {
14830       if (onmove->useColors) {
14831         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14832       }
14833       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14834     }
14835     if (onmove->useColors) {
14836       SendToProgram(onmove->twoMachinesColor, onmove);
14837     }
14838     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14839 //    SendToProgram("go\n", onmove);
14840     onmove->maybeThinking = TRUE;
14841     SetMachineThinkingEnables();
14842
14843     StartClocks();
14844
14845     if(bookHit) { // [HGM] book: simulate book reply
14846         static char bookMove[MSG_SIZ]; // a bit generous?
14847
14848         programStats.nodes = programStats.depth = programStats.time =
14849         programStats.score = programStats.got_only_move = 0;
14850         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14851
14852         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14853         strcat(bookMove, bookHit);
14854         savedMessage = bookMove; // args for deferred call
14855         savedState = onmove;
14856         ScheduleDelayedEvent(DeferredBookMove, 1);
14857     }
14858 }
14859
14860 void
14861 TrainingEvent ()
14862 {
14863     if (gameMode == Training) {
14864       SetTrainingModeOff();
14865       gameMode = PlayFromGameFile;
14866       DisplayMessage("", _("Training mode off"));
14867     } else {
14868       gameMode = Training;
14869       animateTraining = appData.animate;
14870
14871       /* make sure we are not already at the end of the game */
14872       if (currentMove < forwardMostMove) {
14873         SetTrainingModeOn();
14874         DisplayMessage("", _("Training mode on"));
14875       } else {
14876         gameMode = PlayFromGameFile;
14877         DisplayError(_("Already at end of game"), 0);
14878       }
14879     }
14880     ModeHighlight();
14881 }
14882
14883 void
14884 IcsClientEvent ()
14885 {
14886     if (!appData.icsActive) return;
14887     switch (gameMode) {
14888       case IcsPlayingWhite:
14889       case IcsPlayingBlack:
14890       case IcsObserving:
14891       case IcsIdle:
14892       case BeginningOfGame:
14893       case IcsExamining:
14894         return;
14895
14896       case EditGame:
14897         break;
14898
14899       case EditPosition:
14900         EditPositionDone(TRUE);
14901         break;
14902
14903       case AnalyzeMode:
14904       case AnalyzeFile:
14905         ExitAnalyzeMode();
14906         break;
14907
14908       default:
14909         EditGameEvent();
14910         break;
14911     }
14912
14913     gameMode = IcsIdle;
14914     ModeHighlight();
14915     return;
14916 }
14917
14918 void
14919 EditGameEvent ()
14920 {
14921     int i;
14922
14923     switch (gameMode) {
14924       case Training:
14925         SetTrainingModeOff();
14926         break;
14927       case MachinePlaysWhite:
14928       case MachinePlaysBlack:
14929       case BeginningOfGame:
14930         SendToProgram("force\n", &first);
14931         SetUserThinkingEnables();
14932         break;
14933       case PlayFromGameFile:
14934         (void) StopLoadGameTimer();
14935         if (gameFileFP != NULL) {
14936             gameFileFP = NULL;
14937         }
14938         break;
14939       case EditPosition:
14940         EditPositionDone(TRUE);
14941         break;
14942       case AnalyzeMode:
14943       case AnalyzeFile:
14944         ExitAnalyzeMode();
14945         SendToProgram("force\n", &first);
14946         break;
14947       case TwoMachinesPlay:
14948         GameEnds(EndOfFile, NULL, GE_PLAYER);
14949         ResurrectChessProgram();
14950         SetUserThinkingEnables();
14951         break;
14952       case EndOfGame:
14953         ResurrectChessProgram();
14954         break;
14955       case IcsPlayingBlack:
14956       case IcsPlayingWhite:
14957         DisplayError(_("Warning: You are still playing a game"), 0);
14958         break;
14959       case IcsObserving:
14960         DisplayError(_("Warning: You are still observing a game"), 0);
14961         break;
14962       case IcsExamining:
14963         DisplayError(_("Warning: You are still examining a game"), 0);
14964         break;
14965       case IcsIdle:
14966         break;
14967       case EditGame:
14968       default:
14969         return;
14970     }
14971
14972     pausing = FALSE;
14973     StopClocks();
14974     first.offeredDraw = second.offeredDraw = 0;
14975
14976     if (gameMode == PlayFromGameFile) {
14977         whiteTimeRemaining = timeRemaining[0][currentMove];
14978         blackTimeRemaining = timeRemaining[1][currentMove];
14979         DisplayTitle("");
14980     }
14981
14982     if (gameMode == MachinePlaysWhite ||
14983         gameMode == MachinePlaysBlack ||
14984         gameMode == TwoMachinesPlay ||
14985         gameMode == EndOfGame) {
14986         i = forwardMostMove;
14987         while (i > currentMove) {
14988             SendToProgram("undo\n", &first);
14989             i--;
14990         }
14991         if(!adjustedClock) {
14992         whiteTimeRemaining = timeRemaining[0][currentMove];
14993         blackTimeRemaining = timeRemaining[1][currentMove];
14994         DisplayBothClocks();
14995         }
14996         if (whiteFlag || blackFlag) {
14997             whiteFlag = blackFlag = 0;
14998         }
14999         DisplayTitle("");
15000     }
15001
15002     gameMode = EditGame;
15003     ModeHighlight();
15004     SetGameInfo();
15005 }
15006
15007
15008 void
15009 EditPositionEvent ()
15010 {
15011     if (gameMode == EditPosition) {
15012         EditGameEvent();
15013         return;
15014     }
15015
15016     EditGameEvent();
15017     if (gameMode != EditGame) return;
15018
15019     gameMode = EditPosition;
15020     ModeHighlight();
15021     SetGameInfo();
15022     if (currentMove > 0)
15023       CopyBoard(boards[0], boards[currentMove]);
15024
15025     blackPlaysFirst = !WhiteOnMove(currentMove);
15026     ResetClocks();
15027     currentMove = forwardMostMove = backwardMostMove = 0;
15028     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15029     DisplayMove(-1);
15030     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15031 }
15032
15033 void
15034 ExitAnalyzeMode ()
15035 {
15036     /* [DM] icsEngineAnalyze - possible call from other functions */
15037     if (appData.icsEngineAnalyze) {
15038         appData.icsEngineAnalyze = FALSE;
15039
15040         DisplayMessage("",_("Close ICS engine analyze..."));
15041     }
15042     if (first.analysisSupport && first.analyzing) {
15043       SendToBoth("exit\n");
15044       first.analyzing = second.analyzing = FALSE;
15045     }
15046     thinkOutput[0] = NULLCHAR;
15047 }
15048
15049 void
15050 EditPositionDone (Boolean fakeRights)
15051 {
15052     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15053
15054     startedFromSetupPosition = TRUE;
15055     InitChessProgram(&first, FALSE);
15056     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15057       boards[0][EP_STATUS] = EP_NONE;
15058       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15059       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15060         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15061         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15062       } else boards[0][CASTLING][2] = NoRights;
15063       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15064         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15065         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15066       } else boards[0][CASTLING][5] = NoRights;
15067       if(gameInfo.variant == VariantSChess) {
15068         int i;
15069         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15070           boards[0][VIRGIN][i] = 0;
15071           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15072           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15073         }
15074       }
15075     }
15076     SendToProgram("force\n", &first);
15077     if (blackPlaysFirst) {
15078         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15079         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15080         currentMove = forwardMostMove = backwardMostMove = 1;
15081         CopyBoard(boards[1], boards[0]);
15082     } else {
15083         currentMove = forwardMostMove = backwardMostMove = 0;
15084     }
15085     SendBoard(&first, forwardMostMove);
15086     if (appData.debugMode) {
15087         fprintf(debugFP, "EditPosDone\n");
15088     }
15089     DisplayTitle("");
15090     DisplayMessage("", "");
15091     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15092     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15093     gameMode = EditGame;
15094     ModeHighlight();
15095     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15096     ClearHighlights(); /* [AS] */
15097 }
15098
15099 /* Pause for `ms' milliseconds */
15100 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15101 void
15102 TimeDelay (long ms)
15103 {
15104     TimeMark m1, m2;
15105
15106     GetTimeMark(&m1);
15107     do {
15108         GetTimeMark(&m2);
15109     } while (SubtractTimeMarks(&m2, &m1) < ms);
15110 }
15111
15112 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15113 void
15114 SendMultiLineToICS (char *buf)
15115 {
15116     char temp[MSG_SIZ+1], *p;
15117     int len;
15118
15119     len = strlen(buf);
15120     if (len > MSG_SIZ)
15121       len = MSG_SIZ;
15122
15123     strncpy(temp, buf, len);
15124     temp[len] = 0;
15125
15126     p = temp;
15127     while (*p) {
15128         if (*p == '\n' || *p == '\r')
15129           *p = ' ';
15130         ++p;
15131     }
15132
15133     strcat(temp, "\n");
15134     SendToICS(temp);
15135     SendToPlayer(temp, strlen(temp));
15136 }
15137
15138 void
15139 SetWhiteToPlayEvent ()
15140 {
15141     if (gameMode == EditPosition) {
15142         blackPlaysFirst = FALSE;
15143         DisplayBothClocks();    /* works because currentMove is 0 */
15144     } else if (gameMode == IcsExamining) {
15145         SendToICS(ics_prefix);
15146         SendToICS("tomove white\n");
15147     }
15148 }
15149
15150 void
15151 SetBlackToPlayEvent ()
15152 {
15153     if (gameMode == EditPosition) {
15154         blackPlaysFirst = TRUE;
15155         currentMove = 1;        /* kludge */
15156         DisplayBothClocks();
15157         currentMove = 0;
15158     } else if (gameMode == IcsExamining) {
15159         SendToICS(ics_prefix);
15160         SendToICS("tomove black\n");
15161     }
15162 }
15163
15164 void
15165 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15166 {
15167     char buf[MSG_SIZ];
15168     ChessSquare piece = boards[0][y][x];
15169     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15170     static int lastVariant;
15171
15172     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15173
15174     switch (selection) {
15175       case ClearBoard:
15176         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15177         MarkTargetSquares(1);
15178         CopyBoard(currentBoard, boards[0]);
15179         CopyBoard(menuBoard, initialPosition);
15180         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15181             SendToICS(ics_prefix);
15182             SendToICS("bsetup clear\n");
15183         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15184             SendToICS(ics_prefix);
15185             SendToICS("clearboard\n");
15186         } else {
15187             int nonEmpty = 0;
15188             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15189                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15190                 for (y = 0; y < BOARD_HEIGHT; y++) {
15191                     if (gameMode == IcsExamining) {
15192                         if (boards[currentMove][y][x] != EmptySquare) {
15193                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15194                                     AAA + x, ONE + y);
15195                             SendToICS(buf);
15196                         }
15197                     } else if(boards[0][y][x] != DarkSquare) {
15198                         if(boards[0][y][x] != p) nonEmpty++;
15199                         boards[0][y][x] = p;
15200                     }
15201                 }
15202             }
15203             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15204                 int r;
15205                 for(r = 0; r < BOARD_HEIGHT; r++) {
15206                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15207                     ChessSquare p = menuBoard[r][x];
15208                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15209                   }
15210                 }
15211                 DisplayMessage("Clicking clock again restores position", "");
15212                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15213                 if(!nonEmpty) { // asked to clear an empty board
15214                     CopyBoard(boards[0], menuBoard);
15215                 } else
15216                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15217                     CopyBoard(boards[0], initialPosition);
15218                 } else
15219                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15220                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15221                     CopyBoard(boards[0], erasedBoard);
15222                 } else
15223                     CopyBoard(erasedBoard, currentBoard);
15224
15225             }
15226         }
15227         if (gameMode == EditPosition) {
15228             DrawPosition(FALSE, boards[0]);
15229         }
15230         break;
15231
15232       case WhitePlay:
15233         SetWhiteToPlayEvent();
15234         break;
15235
15236       case BlackPlay:
15237         SetBlackToPlayEvent();
15238         break;
15239
15240       case EmptySquare:
15241         if (gameMode == IcsExamining) {
15242             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15243             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15244             SendToICS(buf);
15245         } else {
15246             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15247                 if(x == BOARD_LEFT-2) {
15248                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15249                     boards[0][y][1] = 0;
15250                 } else
15251                 if(x == BOARD_RGHT+1) {
15252                     if(y >= gameInfo.holdingsSize) break;
15253                     boards[0][y][BOARD_WIDTH-2] = 0;
15254                 } else break;
15255             }
15256             boards[0][y][x] = EmptySquare;
15257             DrawPosition(FALSE, boards[0]);
15258         }
15259         break;
15260
15261       case PromotePiece:
15262         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15263            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15264             selection = (ChessSquare) (PROMOTED piece);
15265         } else if(piece == EmptySquare) selection = WhiteSilver;
15266         else selection = (ChessSquare)((int)piece - 1);
15267         goto defaultlabel;
15268
15269       case DemotePiece:
15270         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15271            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15272             selection = (ChessSquare) (DEMOTED piece);
15273         } else if(piece == EmptySquare) selection = BlackSilver;
15274         else selection = (ChessSquare)((int)piece + 1);
15275         goto defaultlabel;
15276
15277       case WhiteQueen:
15278       case BlackQueen:
15279         if(gameInfo.variant == VariantShatranj ||
15280            gameInfo.variant == VariantXiangqi  ||
15281            gameInfo.variant == VariantCourier  ||
15282            gameInfo.variant == VariantASEAN    ||
15283            gameInfo.variant == VariantMakruk     )
15284             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15285         goto defaultlabel;
15286
15287       case WhiteKing:
15288       case BlackKing:
15289         if(gameInfo.variant == VariantXiangqi)
15290             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15291         if(gameInfo.variant == VariantKnightmate)
15292             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15293       default:
15294         defaultlabel:
15295         if (gameMode == IcsExamining) {
15296             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15297             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15298                      PieceToChar(selection), AAA + x, ONE + y);
15299             SendToICS(buf);
15300         } else {
15301             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15302                 int n;
15303                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15304                     n = PieceToNumber(selection - BlackPawn);
15305                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15306                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15307                     boards[0][BOARD_HEIGHT-1-n][1]++;
15308                 } else
15309                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15310                     n = PieceToNumber(selection);
15311                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15312                     boards[0][n][BOARD_WIDTH-1] = selection;
15313                     boards[0][n][BOARD_WIDTH-2]++;
15314                 }
15315             } else
15316             boards[0][y][x] = selection;
15317             DrawPosition(TRUE, boards[0]);
15318             ClearHighlights();
15319             fromX = fromY = -1;
15320         }
15321         break;
15322     }
15323 }
15324
15325
15326 void
15327 DropMenuEvent (ChessSquare selection, int x, int y)
15328 {
15329     ChessMove moveType;
15330
15331     switch (gameMode) {
15332       case IcsPlayingWhite:
15333       case MachinePlaysBlack:
15334         if (!WhiteOnMove(currentMove)) {
15335             DisplayMoveError(_("It is Black's turn"));
15336             return;
15337         }
15338         moveType = WhiteDrop;
15339         break;
15340       case IcsPlayingBlack:
15341       case MachinePlaysWhite:
15342         if (WhiteOnMove(currentMove)) {
15343             DisplayMoveError(_("It is White's turn"));
15344             return;
15345         }
15346         moveType = BlackDrop;
15347         break;
15348       case EditGame:
15349         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15350         break;
15351       default:
15352         return;
15353     }
15354
15355     if (moveType == BlackDrop && selection < BlackPawn) {
15356       selection = (ChessSquare) ((int) selection
15357                                  + (int) BlackPawn - (int) WhitePawn);
15358     }
15359     if (boards[currentMove][y][x] != EmptySquare) {
15360         DisplayMoveError(_("That square is occupied"));
15361         return;
15362     }
15363
15364     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15365 }
15366
15367 void
15368 AcceptEvent ()
15369 {
15370     /* Accept a pending offer of any kind from opponent */
15371
15372     if (appData.icsActive) {
15373         SendToICS(ics_prefix);
15374         SendToICS("accept\n");
15375     } else if (cmailMsgLoaded) {
15376         if (currentMove == cmailOldMove &&
15377             commentList[cmailOldMove] != NULL &&
15378             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15379                    "Black offers a draw" : "White offers a draw")) {
15380             TruncateGame();
15381             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15382             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15383         } else {
15384             DisplayError(_("There is no pending offer on this move"), 0);
15385             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15386         }
15387     } else {
15388         /* Not used for offers from chess program */
15389     }
15390 }
15391
15392 void
15393 DeclineEvent ()
15394 {
15395     /* Decline a pending offer of any kind from opponent */
15396
15397     if (appData.icsActive) {
15398         SendToICS(ics_prefix);
15399         SendToICS("decline\n");
15400     } else if (cmailMsgLoaded) {
15401         if (currentMove == cmailOldMove &&
15402             commentList[cmailOldMove] != NULL &&
15403             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15404                    "Black offers a draw" : "White offers a draw")) {
15405 #ifdef NOTDEF
15406             AppendComment(cmailOldMove, "Draw declined", TRUE);
15407             DisplayComment(cmailOldMove - 1, "Draw declined");
15408 #endif /*NOTDEF*/
15409         } else {
15410             DisplayError(_("There is no pending offer on this move"), 0);
15411         }
15412     } else {
15413         /* Not used for offers from chess program */
15414     }
15415 }
15416
15417 void
15418 RematchEvent ()
15419 {
15420     /* Issue ICS rematch command */
15421     if (appData.icsActive) {
15422         SendToICS(ics_prefix);
15423         SendToICS("rematch\n");
15424     }
15425 }
15426
15427 void
15428 CallFlagEvent ()
15429 {
15430     /* Call your opponent's flag (claim a win on time) */
15431     if (appData.icsActive) {
15432         SendToICS(ics_prefix);
15433         SendToICS("flag\n");
15434     } else {
15435         switch (gameMode) {
15436           default:
15437             return;
15438           case MachinePlaysWhite:
15439             if (whiteFlag) {
15440                 if (blackFlag)
15441                   GameEnds(GameIsDrawn, "Both players ran out of time",
15442                            GE_PLAYER);
15443                 else
15444                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15445             } else {
15446                 DisplayError(_("Your opponent is not out of time"), 0);
15447             }
15448             break;
15449           case MachinePlaysBlack:
15450             if (blackFlag) {
15451                 if (whiteFlag)
15452                   GameEnds(GameIsDrawn, "Both players ran out of time",
15453                            GE_PLAYER);
15454                 else
15455                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15456             } else {
15457                 DisplayError(_("Your opponent is not out of time"), 0);
15458             }
15459             break;
15460         }
15461     }
15462 }
15463
15464 void
15465 ClockClick (int which)
15466 {       // [HGM] code moved to back-end from winboard.c
15467         if(which) { // black clock
15468           if (gameMode == EditPosition || gameMode == IcsExamining) {
15469             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15470             SetBlackToPlayEvent();
15471           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15472                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15473           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15474           } else if (shiftKey) {
15475             AdjustClock(which, -1);
15476           } else if (gameMode == IcsPlayingWhite ||
15477                      gameMode == MachinePlaysBlack) {
15478             CallFlagEvent();
15479           }
15480         } else { // white clock
15481           if (gameMode == EditPosition || gameMode == IcsExamining) {
15482             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15483             SetWhiteToPlayEvent();
15484           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15485                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15486           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15487           } else if (shiftKey) {
15488             AdjustClock(which, -1);
15489           } else if (gameMode == IcsPlayingBlack ||
15490                    gameMode == MachinePlaysWhite) {
15491             CallFlagEvent();
15492           }
15493         }
15494 }
15495
15496 void
15497 DrawEvent ()
15498 {
15499     /* Offer draw or accept pending draw offer from opponent */
15500
15501     if (appData.icsActive) {
15502         /* Note: tournament rules require draw offers to be
15503            made after you make your move but before you punch
15504            your clock.  Currently ICS doesn't let you do that;
15505            instead, you immediately punch your clock after making
15506            a move, but you can offer a draw at any time. */
15507
15508         SendToICS(ics_prefix);
15509         SendToICS("draw\n");
15510         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15511     } else if (cmailMsgLoaded) {
15512         if (currentMove == cmailOldMove &&
15513             commentList[cmailOldMove] != NULL &&
15514             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15515                    "Black offers a draw" : "White offers a draw")) {
15516             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15517             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15518         } else if (currentMove == cmailOldMove + 1) {
15519             char *offer = WhiteOnMove(cmailOldMove) ?
15520               "White offers a draw" : "Black offers a draw";
15521             AppendComment(currentMove, offer, TRUE);
15522             DisplayComment(currentMove - 1, offer);
15523             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15524         } else {
15525             DisplayError(_("You must make your move before offering a draw"), 0);
15526             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15527         }
15528     } else if (first.offeredDraw) {
15529         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15530     } else {
15531         if (first.sendDrawOffers) {
15532             SendToProgram("draw\n", &first);
15533             userOfferedDraw = TRUE;
15534         }
15535     }
15536 }
15537
15538 void
15539 AdjournEvent ()
15540 {
15541     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15542
15543     if (appData.icsActive) {
15544         SendToICS(ics_prefix);
15545         SendToICS("adjourn\n");
15546     } else {
15547         /* Currently GNU Chess doesn't offer or accept Adjourns */
15548     }
15549 }
15550
15551
15552 void
15553 AbortEvent ()
15554 {
15555     /* Offer Abort or accept pending Abort offer from opponent */
15556
15557     if (appData.icsActive) {
15558         SendToICS(ics_prefix);
15559         SendToICS("abort\n");
15560     } else {
15561         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15562     }
15563 }
15564
15565 void
15566 ResignEvent ()
15567 {
15568     /* Resign.  You can do this even if it's not your turn. */
15569
15570     if (appData.icsActive) {
15571         SendToICS(ics_prefix);
15572         SendToICS("resign\n");
15573     } else {
15574         switch (gameMode) {
15575           case MachinePlaysWhite:
15576             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15577             break;
15578           case MachinePlaysBlack:
15579             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15580             break;
15581           case EditGame:
15582             if (cmailMsgLoaded) {
15583                 TruncateGame();
15584                 if (WhiteOnMove(cmailOldMove)) {
15585                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15586                 } else {
15587                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15588                 }
15589                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15590             }
15591             break;
15592           default:
15593             break;
15594         }
15595     }
15596 }
15597
15598
15599 void
15600 StopObservingEvent ()
15601 {
15602     /* Stop observing current games */
15603     SendToICS(ics_prefix);
15604     SendToICS("unobserve\n");
15605 }
15606
15607 void
15608 StopExaminingEvent ()
15609 {
15610     /* Stop observing current game */
15611     SendToICS(ics_prefix);
15612     SendToICS("unexamine\n");
15613 }
15614
15615 void
15616 ForwardInner (int target)
15617 {
15618     int limit; int oldSeekGraphUp = seekGraphUp;
15619
15620     if (appData.debugMode)
15621         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15622                 target, currentMove, forwardMostMove);
15623
15624     if (gameMode == EditPosition)
15625       return;
15626
15627     seekGraphUp = FALSE;
15628     MarkTargetSquares(1);
15629     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15630
15631     if (gameMode == PlayFromGameFile && !pausing)
15632       PauseEvent();
15633
15634     if (gameMode == IcsExamining && pausing)
15635       limit = pauseExamForwardMostMove;
15636     else
15637       limit = forwardMostMove;
15638
15639     if (target > limit) target = limit;
15640
15641     if (target > 0 && moveList[target - 1][0]) {
15642         int fromX, fromY, toX, toY;
15643         toX = moveList[target - 1][2] - AAA;
15644         toY = moveList[target - 1][3] - ONE;
15645         if (moveList[target - 1][1] == '@') {
15646             if (appData.highlightLastMove) {
15647                 SetHighlights(-1, -1, toX, toY);
15648             }
15649         } else {
15650             int viaX = moveList[target - 1][5] - AAA;
15651             int viaY = moveList[target - 1][6] - ONE;
15652             fromX = moveList[target - 1][0] - AAA;
15653             fromY = moveList[target - 1][1] - ONE;
15654             if (target == currentMove + 1) {
15655                 if(moveList[target - 1][4] == ';') { // multi-leg
15656                     ChessSquare piece = boards[currentMove][viaY][viaX];
15657                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15658                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15659                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15660                     boards[currentMove][viaY][viaX] = piece;
15661                 } else
15662                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15663             }
15664             if (appData.highlightLastMove) {
15665                 SetHighlights(fromX, fromY, toX, toY);
15666             }
15667         }
15668     }
15669     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15670         gameMode == Training || gameMode == PlayFromGameFile ||
15671         gameMode == AnalyzeFile) {
15672         while (currentMove < target) {
15673             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15674             SendMoveToProgram(currentMove++, &first);
15675         }
15676     } else {
15677         currentMove = target;
15678     }
15679
15680     if (gameMode == EditGame || gameMode == EndOfGame) {
15681         whiteTimeRemaining = timeRemaining[0][currentMove];
15682         blackTimeRemaining = timeRemaining[1][currentMove];
15683     }
15684     DisplayBothClocks();
15685     DisplayMove(currentMove - 1);
15686     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15687     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15688     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15689         DisplayComment(currentMove - 1, commentList[currentMove]);
15690     }
15691     ClearMap(); // [HGM] exclude: invalidate map
15692 }
15693
15694
15695 void
15696 ForwardEvent ()
15697 {
15698     if (gameMode == IcsExamining && !pausing) {
15699         SendToICS(ics_prefix);
15700         SendToICS("forward\n");
15701     } else {
15702         ForwardInner(currentMove + 1);
15703     }
15704 }
15705
15706 void
15707 ToEndEvent ()
15708 {
15709     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15710         /* to optimze, we temporarily turn off analysis mode while we feed
15711          * the remaining moves to the engine. Otherwise we get analysis output
15712          * after each move.
15713          */
15714         if (first.analysisSupport) {
15715           SendToProgram("exit\nforce\n", &first);
15716           first.analyzing = FALSE;
15717         }
15718     }
15719
15720     if (gameMode == IcsExamining && !pausing) {
15721         SendToICS(ics_prefix);
15722         SendToICS("forward 999999\n");
15723     } else {
15724         ForwardInner(forwardMostMove);
15725     }
15726
15727     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15728         /* we have fed all the moves, so reactivate analysis mode */
15729         SendToProgram("analyze\n", &first);
15730         first.analyzing = TRUE;
15731         /*first.maybeThinking = TRUE;*/
15732         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15733     }
15734 }
15735
15736 void
15737 BackwardInner (int target)
15738 {
15739     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15740
15741     if (appData.debugMode)
15742         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15743                 target, currentMove, forwardMostMove);
15744
15745     if (gameMode == EditPosition) return;
15746     seekGraphUp = FALSE;
15747     MarkTargetSquares(1);
15748     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15749     if (currentMove <= backwardMostMove) {
15750         ClearHighlights();
15751         DrawPosition(full_redraw, boards[currentMove]);
15752         return;
15753     }
15754     if (gameMode == PlayFromGameFile && !pausing)
15755       PauseEvent();
15756
15757     if (moveList[target][0]) {
15758         int fromX, fromY, toX, toY;
15759         toX = moveList[target][2] - AAA;
15760         toY = moveList[target][3] - ONE;
15761         if (moveList[target][1] == '@') {
15762             if (appData.highlightLastMove) {
15763                 SetHighlights(-1, -1, toX, toY);
15764             }
15765         } else {
15766             fromX = moveList[target][0] - AAA;
15767             fromY = moveList[target][1] - ONE;
15768             if (target == currentMove - 1) {
15769                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15770             }
15771             if (appData.highlightLastMove) {
15772                 SetHighlights(fromX, fromY, toX, toY);
15773             }
15774         }
15775     }
15776     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15777         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15778         while (currentMove > target) {
15779             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15780                 // null move cannot be undone. Reload program with move history before it.
15781                 int i;
15782                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15783                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15784                 }
15785                 SendBoard(&first, i);
15786               if(second.analyzing) SendBoard(&second, i);
15787                 for(currentMove=i; currentMove<target; currentMove++) {
15788                     SendMoveToProgram(currentMove, &first);
15789                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15790                 }
15791                 break;
15792             }
15793             SendToBoth("undo\n");
15794             currentMove--;
15795         }
15796     } else {
15797         currentMove = target;
15798     }
15799
15800     if (gameMode == EditGame || gameMode == EndOfGame) {
15801         whiteTimeRemaining = timeRemaining[0][currentMove];
15802         blackTimeRemaining = timeRemaining[1][currentMove];
15803     }
15804     DisplayBothClocks();
15805     DisplayMove(currentMove - 1);
15806     DrawPosition(full_redraw, boards[currentMove]);
15807     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15808     // [HGM] PV info: routine tests if comment empty
15809     DisplayComment(currentMove - 1, commentList[currentMove]);
15810     ClearMap(); // [HGM] exclude: invalidate map
15811 }
15812
15813 void
15814 BackwardEvent ()
15815 {
15816     if (gameMode == IcsExamining && !pausing) {
15817         SendToICS(ics_prefix);
15818         SendToICS("backward\n");
15819     } else {
15820         BackwardInner(currentMove - 1);
15821     }
15822 }
15823
15824 void
15825 ToStartEvent ()
15826 {
15827     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15828         /* to optimize, we temporarily turn off analysis mode while we undo
15829          * all the moves. Otherwise we get analysis output after each undo.
15830          */
15831         if (first.analysisSupport) {
15832           SendToProgram("exit\nforce\n", &first);
15833           first.analyzing = FALSE;
15834         }
15835     }
15836
15837     if (gameMode == IcsExamining && !pausing) {
15838         SendToICS(ics_prefix);
15839         SendToICS("backward 999999\n");
15840     } else {
15841         BackwardInner(backwardMostMove);
15842     }
15843
15844     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15845         /* we have fed all the moves, so reactivate analysis mode */
15846         SendToProgram("analyze\n", &first);
15847         first.analyzing = TRUE;
15848         /*first.maybeThinking = TRUE;*/
15849         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15850     }
15851 }
15852
15853 void
15854 ToNrEvent (int to)
15855 {
15856   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15857   if (to >= forwardMostMove) to = forwardMostMove;
15858   if (to <= backwardMostMove) to = backwardMostMove;
15859   if (to < currentMove) {
15860     BackwardInner(to);
15861   } else {
15862     ForwardInner(to);
15863   }
15864 }
15865
15866 void
15867 RevertEvent (Boolean annotate)
15868 {
15869     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15870         return;
15871     }
15872     if (gameMode != IcsExamining) {
15873         DisplayError(_("You are not examining a game"), 0);
15874         return;
15875     }
15876     if (pausing) {
15877         DisplayError(_("You can't revert while pausing"), 0);
15878         return;
15879     }
15880     SendToICS(ics_prefix);
15881     SendToICS("revert\n");
15882 }
15883
15884 void
15885 RetractMoveEvent ()
15886 {
15887     switch (gameMode) {
15888       case MachinePlaysWhite:
15889       case MachinePlaysBlack:
15890         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15891             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15892             return;
15893         }
15894         if (forwardMostMove < 2) return;
15895         currentMove = forwardMostMove = forwardMostMove - 2;
15896         whiteTimeRemaining = timeRemaining[0][currentMove];
15897         blackTimeRemaining = timeRemaining[1][currentMove];
15898         DisplayBothClocks();
15899         DisplayMove(currentMove - 1);
15900         ClearHighlights();/*!! could figure this out*/
15901         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15902         SendToProgram("remove\n", &first);
15903         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15904         break;
15905
15906       case BeginningOfGame:
15907       default:
15908         break;
15909
15910       case IcsPlayingWhite:
15911       case IcsPlayingBlack:
15912         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15913             SendToICS(ics_prefix);
15914             SendToICS("takeback 2\n");
15915         } else {
15916             SendToICS(ics_prefix);
15917             SendToICS("takeback 1\n");
15918         }
15919         break;
15920     }
15921 }
15922
15923 void
15924 MoveNowEvent ()
15925 {
15926     ChessProgramState *cps;
15927
15928     switch (gameMode) {
15929       case MachinePlaysWhite:
15930         if (!WhiteOnMove(forwardMostMove)) {
15931             DisplayError(_("It is your turn"), 0);
15932             return;
15933         }
15934         cps = &first;
15935         break;
15936       case MachinePlaysBlack:
15937         if (WhiteOnMove(forwardMostMove)) {
15938             DisplayError(_("It is your turn"), 0);
15939             return;
15940         }
15941         cps = &first;
15942         break;
15943       case TwoMachinesPlay:
15944         if (WhiteOnMove(forwardMostMove) ==
15945             (first.twoMachinesColor[0] == 'w')) {
15946             cps = &first;
15947         } else {
15948             cps = &second;
15949         }
15950         break;
15951       case BeginningOfGame:
15952       default:
15953         return;
15954     }
15955     SendToProgram("?\n", cps);
15956 }
15957
15958 void
15959 TruncateGameEvent ()
15960 {
15961     EditGameEvent();
15962     if (gameMode != EditGame) return;
15963     TruncateGame();
15964 }
15965
15966 void
15967 TruncateGame ()
15968 {
15969     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15970     if (forwardMostMove > currentMove) {
15971         if (gameInfo.resultDetails != NULL) {
15972             free(gameInfo.resultDetails);
15973             gameInfo.resultDetails = NULL;
15974             gameInfo.result = GameUnfinished;
15975         }
15976         forwardMostMove = currentMove;
15977         HistorySet(parseList, backwardMostMove, forwardMostMove,
15978                    currentMove-1);
15979     }
15980 }
15981
15982 void
15983 HintEvent ()
15984 {
15985     if (appData.noChessProgram) return;
15986     switch (gameMode) {
15987       case MachinePlaysWhite:
15988         if (WhiteOnMove(forwardMostMove)) {
15989             DisplayError(_("Wait until your turn."), 0);
15990             return;
15991         }
15992         break;
15993       case BeginningOfGame:
15994       case MachinePlaysBlack:
15995         if (!WhiteOnMove(forwardMostMove)) {
15996             DisplayError(_("Wait until your turn."), 0);
15997             return;
15998         }
15999         break;
16000       default:
16001         DisplayError(_("No hint available"), 0);
16002         return;
16003     }
16004     SendToProgram("hint\n", &first);
16005     hintRequested = TRUE;
16006 }
16007
16008 int
16009 SaveSelected (FILE *g, int dummy, char *dummy2)
16010 {
16011     ListGame * lg = (ListGame *) gameList.head;
16012     int nItem, cnt=0;
16013     FILE *f;
16014
16015     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16016         DisplayError(_("Game list not loaded or empty"), 0);
16017         return 0;
16018     }
16019
16020     creatingBook = TRUE; // suppresses stuff during load game
16021
16022     /* Get list size */
16023     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16024         if(lg->position >= 0) { // selected?
16025             LoadGame(f, nItem, "", TRUE);
16026             SaveGamePGN2(g); // leaves g open
16027             cnt++; DoEvents();
16028         }
16029         lg = (ListGame *) lg->node.succ;
16030     }
16031
16032     fclose(g);
16033     creatingBook = FALSE;
16034
16035     return cnt;
16036 }
16037
16038 void
16039 CreateBookEvent ()
16040 {
16041     ListGame * lg = (ListGame *) gameList.head;
16042     FILE *f, *g;
16043     int nItem;
16044     static int secondTime = FALSE;
16045
16046     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16047         DisplayError(_("Game list not loaded or empty"), 0);
16048         return;
16049     }
16050
16051     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16052         fclose(g);
16053         secondTime++;
16054         DisplayNote(_("Book file exists! Try again for overwrite."));
16055         return;
16056     }
16057
16058     creatingBook = TRUE;
16059     secondTime = FALSE;
16060
16061     /* Get list size */
16062     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16063         if(lg->position >= 0) {
16064             LoadGame(f, nItem, "", TRUE);
16065             AddGameToBook(TRUE);
16066             DoEvents();
16067         }
16068         lg = (ListGame *) lg->node.succ;
16069     }
16070
16071     creatingBook = FALSE;
16072     FlushBook();
16073 }
16074
16075 void
16076 BookEvent ()
16077 {
16078     if (appData.noChessProgram) return;
16079     switch (gameMode) {
16080       case MachinePlaysWhite:
16081         if (WhiteOnMove(forwardMostMove)) {
16082             DisplayError(_("Wait until your turn."), 0);
16083             return;
16084         }
16085         break;
16086       case BeginningOfGame:
16087       case MachinePlaysBlack:
16088         if (!WhiteOnMove(forwardMostMove)) {
16089             DisplayError(_("Wait until your turn."), 0);
16090             return;
16091         }
16092         break;
16093       case EditPosition:
16094         EditPositionDone(TRUE);
16095         break;
16096       case TwoMachinesPlay:
16097         return;
16098       default:
16099         break;
16100     }
16101     SendToProgram("bk\n", &first);
16102     bookOutput[0] = NULLCHAR;
16103     bookRequested = TRUE;
16104 }
16105
16106 void
16107 AboutGameEvent ()
16108 {
16109     char *tags = PGNTags(&gameInfo);
16110     TagsPopUp(tags, CmailMsg());
16111     free(tags);
16112 }
16113
16114 /* end button procedures */
16115
16116 void
16117 PrintPosition (FILE *fp, int move)
16118 {
16119     int i, j;
16120
16121     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16122         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16123             char c = PieceToChar(boards[move][i][j]);
16124             fputc(c == 'x' ? '.' : c, fp);
16125             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16126         }
16127     }
16128     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16129       fprintf(fp, "white to play\n");
16130     else
16131       fprintf(fp, "black to play\n");
16132 }
16133
16134 void
16135 PrintOpponents (FILE *fp)
16136 {
16137     if (gameInfo.white != NULL) {
16138         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16139     } else {
16140         fprintf(fp, "\n");
16141     }
16142 }
16143
16144 /* Find last component of program's own name, using some heuristics */
16145 void
16146 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16147 {
16148     char *p, *q, c;
16149     int local = (strcmp(host, "localhost") == 0);
16150     while (!local && (p = strchr(prog, ';')) != NULL) {
16151         p++;
16152         while (*p == ' ') p++;
16153         prog = p;
16154     }
16155     if (*prog == '"' || *prog == '\'') {
16156         q = strchr(prog + 1, *prog);
16157     } else {
16158         q = strchr(prog, ' ');
16159     }
16160     if (q == NULL) q = prog + strlen(prog);
16161     p = q;
16162     while (p >= prog && *p != '/' && *p != '\\') p--;
16163     p++;
16164     if(p == prog && *p == '"') p++;
16165     c = *q; *q = 0;
16166     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16167     memcpy(buf, p, q - p);
16168     buf[q - p] = NULLCHAR;
16169     if (!local) {
16170         strcat(buf, "@");
16171         strcat(buf, host);
16172     }
16173 }
16174
16175 char *
16176 TimeControlTagValue ()
16177 {
16178     char buf[MSG_SIZ];
16179     if (!appData.clockMode) {
16180       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16181     } else if (movesPerSession > 0) {
16182       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16183     } else if (timeIncrement == 0) {
16184       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16185     } else {
16186       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16187     }
16188     return StrSave(buf);
16189 }
16190
16191 void
16192 SetGameInfo ()
16193 {
16194     /* This routine is used only for certain modes */
16195     VariantClass v = gameInfo.variant;
16196     ChessMove r = GameUnfinished;
16197     char *p = NULL;
16198
16199     if(keepInfo) return;
16200
16201     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16202         r = gameInfo.result;
16203         p = gameInfo.resultDetails;
16204         gameInfo.resultDetails = NULL;
16205     }
16206     ClearGameInfo(&gameInfo);
16207     gameInfo.variant = v;
16208
16209     switch (gameMode) {
16210       case MachinePlaysWhite:
16211         gameInfo.event = StrSave( appData.pgnEventHeader );
16212         gameInfo.site = StrSave(HostName());
16213         gameInfo.date = PGNDate();
16214         gameInfo.round = StrSave("-");
16215         gameInfo.white = StrSave(first.tidy);
16216         gameInfo.black = StrSave(UserName());
16217         gameInfo.timeControl = TimeControlTagValue();
16218         break;
16219
16220       case MachinePlaysBlack:
16221         gameInfo.event = StrSave( appData.pgnEventHeader );
16222         gameInfo.site = StrSave(HostName());
16223         gameInfo.date = PGNDate();
16224         gameInfo.round = StrSave("-");
16225         gameInfo.white = StrSave(UserName());
16226         gameInfo.black = StrSave(first.tidy);
16227         gameInfo.timeControl = TimeControlTagValue();
16228         break;
16229
16230       case TwoMachinesPlay:
16231         gameInfo.event = StrSave( appData.pgnEventHeader );
16232         gameInfo.site = StrSave(HostName());
16233         gameInfo.date = PGNDate();
16234         if (roundNr > 0) {
16235             char buf[MSG_SIZ];
16236             snprintf(buf, MSG_SIZ, "%d", roundNr);
16237             gameInfo.round = StrSave(buf);
16238         } else {
16239             gameInfo.round = StrSave("-");
16240         }
16241         if (first.twoMachinesColor[0] == 'w') {
16242             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16243             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16244         } else {
16245             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16246             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16247         }
16248         gameInfo.timeControl = TimeControlTagValue();
16249         break;
16250
16251       case EditGame:
16252         gameInfo.event = StrSave("Edited game");
16253         gameInfo.site = StrSave(HostName());
16254         gameInfo.date = PGNDate();
16255         gameInfo.round = StrSave("-");
16256         gameInfo.white = StrSave("-");
16257         gameInfo.black = StrSave("-");
16258         gameInfo.result = r;
16259         gameInfo.resultDetails = p;
16260         break;
16261
16262       case EditPosition:
16263         gameInfo.event = StrSave("Edited position");
16264         gameInfo.site = StrSave(HostName());
16265         gameInfo.date = PGNDate();
16266         gameInfo.round = StrSave("-");
16267         gameInfo.white = StrSave("-");
16268         gameInfo.black = StrSave("-");
16269         break;
16270
16271       case IcsPlayingWhite:
16272       case IcsPlayingBlack:
16273       case IcsObserving:
16274       case IcsExamining:
16275         break;
16276
16277       case PlayFromGameFile:
16278         gameInfo.event = StrSave("Game from non-PGN file");
16279         gameInfo.site = StrSave(HostName());
16280         gameInfo.date = PGNDate();
16281         gameInfo.round = StrSave("-");
16282         gameInfo.white = StrSave("?");
16283         gameInfo.black = StrSave("?");
16284         break;
16285
16286       default:
16287         break;
16288     }
16289 }
16290
16291 void
16292 ReplaceComment (int index, char *text)
16293 {
16294     int len;
16295     char *p;
16296     float score;
16297
16298     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16299        pvInfoList[index-1].depth == len &&
16300        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16301        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16302     while (*text == '\n') text++;
16303     len = strlen(text);
16304     while (len > 0 && text[len - 1] == '\n') len--;
16305
16306     if (commentList[index] != NULL)
16307       free(commentList[index]);
16308
16309     if (len == 0) {
16310         commentList[index] = NULL;
16311         return;
16312     }
16313   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16314       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16315       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16316     commentList[index] = (char *) malloc(len + 2);
16317     strncpy(commentList[index], text, len);
16318     commentList[index][len] = '\n';
16319     commentList[index][len + 1] = NULLCHAR;
16320   } else {
16321     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16322     char *p;
16323     commentList[index] = (char *) malloc(len + 7);
16324     safeStrCpy(commentList[index], "{\n", 3);
16325     safeStrCpy(commentList[index]+2, text, len+1);
16326     commentList[index][len+2] = NULLCHAR;
16327     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16328     strcat(commentList[index], "\n}\n");
16329   }
16330 }
16331
16332 void
16333 CrushCRs (char *text)
16334 {
16335   char *p = text;
16336   char *q = text;
16337   char ch;
16338
16339   do {
16340     ch = *p++;
16341     if (ch == '\r') continue;
16342     *q++ = ch;
16343   } while (ch != '\0');
16344 }
16345
16346 void
16347 AppendComment (int index, char *text, Boolean addBraces)
16348 /* addBraces  tells if we should add {} */
16349 {
16350     int oldlen, len;
16351     char *old;
16352
16353 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16354     if(addBraces == 3) addBraces = 0; else // force appending literally
16355     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16356
16357     CrushCRs(text);
16358     while (*text == '\n') text++;
16359     len = strlen(text);
16360     while (len > 0 && text[len - 1] == '\n') len--;
16361     text[len] = NULLCHAR;
16362
16363     if (len == 0) return;
16364
16365     if (commentList[index] != NULL) {
16366       Boolean addClosingBrace = addBraces;
16367         old = commentList[index];
16368         oldlen = strlen(old);
16369         while(commentList[index][oldlen-1] ==  '\n')
16370           commentList[index][--oldlen] = NULLCHAR;
16371         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16372         safeStrCpy(commentList[index], old, oldlen + len + 6);
16373         free(old);
16374         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16375         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16376           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16377           while (*text == '\n') { text++; len--; }
16378           commentList[index][--oldlen] = NULLCHAR;
16379       }
16380         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16381         else          strcat(commentList[index], "\n");
16382         strcat(commentList[index], text);
16383         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16384         else          strcat(commentList[index], "\n");
16385     } else {
16386         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16387         if(addBraces)
16388           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16389         else commentList[index][0] = NULLCHAR;
16390         strcat(commentList[index], text);
16391         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16392         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16393     }
16394 }
16395
16396 static char *
16397 FindStr (char * text, char * sub_text)
16398 {
16399     char * result = strstr( text, sub_text );
16400
16401     if( result != NULL ) {
16402         result += strlen( sub_text );
16403     }
16404
16405     return result;
16406 }
16407
16408 /* [AS] Try to extract PV info from PGN comment */
16409 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16410 char *
16411 GetInfoFromComment (int index, char * text)
16412 {
16413     char * sep = text, *p;
16414
16415     if( text != NULL && index > 0 ) {
16416         int score = 0;
16417         int depth = 0;
16418         int time = -1, sec = 0, deci;
16419         char * s_eval = FindStr( text, "[%eval " );
16420         char * s_emt = FindStr( text, "[%emt " );
16421 #if 0
16422         if( s_eval != NULL || s_emt != NULL ) {
16423 #else
16424         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16425 #endif
16426             /* New style */
16427             char delim;
16428
16429             if( s_eval != NULL ) {
16430                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16431                     return text;
16432                 }
16433
16434                 if( delim != ']' ) {
16435                     return text;
16436                 }
16437             }
16438
16439             if( s_emt != NULL ) {
16440             }
16441                 return text;
16442         }
16443         else {
16444             /* We expect something like: [+|-]nnn.nn/dd */
16445             int score_lo = 0;
16446
16447             if(*text != '{') return text; // [HGM] braces: must be normal comment
16448
16449             sep = strchr( text, '/' );
16450             if( sep == NULL || sep < (text+4) ) {
16451                 return text;
16452             }
16453
16454             p = text;
16455             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16456             if(p[1] == '(') { // comment starts with PV
16457                p = strchr(p, ')'); // locate end of PV
16458                if(p == NULL || sep < p+5) return text;
16459                // at this point we have something like "{(.*) +0.23/6 ..."
16460                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16461                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16462                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16463             }
16464             time = -1; sec = -1; deci = -1;
16465             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16466                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16467                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16468                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16469                 return text;
16470             }
16471
16472             if( score_lo < 0 || score_lo >= 100 ) {
16473                 return text;
16474             }
16475
16476             if(sec >= 0) time = 600*time + 10*sec; else
16477             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16478
16479             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16480
16481             /* [HGM] PV time: now locate end of PV info */
16482             while( *++sep >= '0' && *sep <= '9'); // strip depth
16483             if(time >= 0)
16484             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16485             if(sec >= 0)
16486             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16487             if(deci >= 0)
16488             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16489             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16490         }
16491
16492         if( depth <= 0 ) {
16493             return text;
16494         }
16495
16496         if( time < 0 ) {
16497             time = -1;
16498         }
16499
16500         pvInfoList[index-1].depth = depth;
16501         pvInfoList[index-1].score = score;
16502         pvInfoList[index-1].time  = 10*time; // centi-sec
16503         if(*sep == '}') *sep = 0; else *--sep = '{';
16504         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16505     }
16506     return sep;
16507 }
16508
16509 void
16510 SendToProgram (char *message, ChessProgramState *cps)
16511 {
16512     int count, outCount, error;
16513     char buf[MSG_SIZ];
16514
16515     if (cps->pr == NoProc) return;
16516     Attention(cps);
16517
16518     if (appData.debugMode) {
16519         TimeMark now;
16520         GetTimeMark(&now);
16521         fprintf(debugFP, "%ld >%-6s: %s",
16522                 SubtractTimeMarks(&now, &programStartTime),
16523                 cps->which, message);
16524         if(serverFP)
16525             fprintf(serverFP, "%ld >%-6s: %s",
16526                 SubtractTimeMarks(&now, &programStartTime),
16527                 cps->which, message), fflush(serverFP);
16528     }
16529
16530     count = strlen(message);
16531     outCount = OutputToProcess(cps->pr, message, count, &error);
16532     if (outCount < count && !exiting
16533                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16534       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16535       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16536         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16537             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16538                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16539                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16540                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16541             } else {
16542                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16543                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16544                 gameInfo.result = res;
16545             }
16546             gameInfo.resultDetails = StrSave(buf);
16547         }
16548         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16549         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16550     }
16551 }
16552
16553 void
16554 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16555 {
16556     char *end_str;
16557     char buf[MSG_SIZ];
16558     ChessProgramState *cps = (ChessProgramState *)closure;
16559
16560     if (isr != cps->isr) return; /* Killed intentionally */
16561     if (count <= 0) {
16562         if (count == 0) {
16563             RemoveInputSource(cps->isr);
16564             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16565                     _(cps->which), cps->program);
16566             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16567             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16568                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16569                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16570                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16571                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16572                 } else {
16573                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16574                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16575                     gameInfo.result = res;
16576                 }
16577                 gameInfo.resultDetails = StrSave(buf);
16578             }
16579             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16580             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16581         } else {
16582             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16583                     _(cps->which), cps->program);
16584             RemoveInputSource(cps->isr);
16585
16586             /* [AS] Program is misbehaving badly... kill it */
16587             if( count == -2 ) {
16588                 DestroyChildProcess( cps->pr, 9 );
16589                 cps->pr = NoProc;
16590             }
16591
16592             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16593         }
16594         return;
16595     }
16596
16597     if ((end_str = strchr(message, '\r')) != NULL)
16598       *end_str = NULLCHAR;
16599     if ((end_str = strchr(message, '\n')) != NULL)
16600       *end_str = NULLCHAR;
16601
16602     if (appData.debugMode) {
16603         TimeMark now; int print = 1;
16604         char *quote = ""; char c; int i;
16605
16606         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16607                 char start = message[0];
16608                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16609                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16610                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16611                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16612                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16613                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16614                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16615                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16616                    sscanf(message, "hint: %c", &c)!=1 &&
16617                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16618                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16619                     print = (appData.engineComments >= 2);
16620                 }
16621                 message[0] = start; // restore original message
16622         }
16623         if(print) {
16624                 GetTimeMark(&now);
16625                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16626                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16627                         quote,
16628                         message);
16629                 if(serverFP)
16630                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16631                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16632                         quote,
16633                         message), fflush(serverFP);
16634         }
16635     }
16636
16637     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16638     if (appData.icsEngineAnalyze) {
16639         if (strstr(message, "whisper") != NULL ||
16640              strstr(message, "kibitz") != NULL ||
16641             strstr(message, "tellics") != NULL) return;
16642     }
16643
16644     HandleMachineMove(message, cps);
16645 }
16646
16647
16648 void
16649 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16650 {
16651     char buf[MSG_SIZ];
16652     int seconds;
16653
16654     if( timeControl_2 > 0 ) {
16655         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16656             tc = timeControl_2;
16657         }
16658     }
16659     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16660     inc /= cps->timeOdds;
16661     st  /= cps->timeOdds;
16662
16663     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16664
16665     if (st > 0) {
16666       /* Set exact time per move, normally using st command */
16667       if (cps->stKludge) {
16668         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16669         seconds = st % 60;
16670         if (seconds == 0) {
16671           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16672         } else {
16673           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16674         }
16675       } else {
16676         snprintf(buf, MSG_SIZ, "st %d\n", st);
16677       }
16678     } else {
16679       /* Set conventional or incremental time control, using level command */
16680       if (seconds == 0) {
16681         /* Note old gnuchess bug -- minutes:seconds used to not work.
16682            Fixed in later versions, but still avoid :seconds
16683            when seconds is 0. */
16684         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16685       } else {
16686         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16687                  seconds, inc/1000.);
16688       }
16689     }
16690     SendToProgram(buf, cps);
16691
16692     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16693     /* Orthogonally, limit search to given depth */
16694     if (sd > 0) {
16695       if (cps->sdKludge) {
16696         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16697       } else {
16698         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16699       }
16700       SendToProgram(buf, cps);
16701     }
16702
16703     if(cps->nps >= 0) { /* [HGM] nps */
16704         if(cps->supportsNPS == FALSE)
16705           cps->nps = -1; // don't use if engine explicitly says not supported!
16706         else {
16707           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16708           SendToProgram(buf, cps);
16709         }
16710     }
16711 }
16712
16713 ChessProgramState *
16714 WhitePlayer ()
16715 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16716 {
16717     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16718        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16719         return &second;
16720     return &first;
16721 }
16722
16723 void
16724 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16725 {
16726     char message[MSG_SIZ];
16727     long time, otime;
16728
16729     /* Note: this routine must be called when the clocks are stopped
16730        or when they have *just* been set or switched; otherwise
16731        it will be off by the time since the current tick started.
16732     */
16733     if (machineWhite) {
16734         time = whiteTimeRemaining / 10;
16735         otime = blackTimeRemaining / 10;
16736     } else {
16737         time = blackTimeRemaining / 10;
16738         otime = whiteTimeRemaining / 10;
16739     }
16740     /* [HGM] translate opponent's time by time-odds factor */
16741     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16742
16743     if (time <= 0) time = 1;
16744     if (otime <= 0) otime = 1;
16745
16746     snprintf(message, MSG_SIZ, "time %ld\n", time);
16747     SendToProgram(message, cps);
16748
16749     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16750     SendToProgram(message, cps);
16751 }
16752
16753 char *
16754 EngineDefinedVariant (ChessProgramState *cps, int n)
16755 {   // return name of n-th unknown variant that engine supports
16756     static char buf[MSG_SIZ];
16757     char *p, *s = cps->variants;
16758     if(!s) return NULL;
16759     do { // parse string from variants feature
16760       VariantClass v;
16761         p = strchr(s, ',');
16762         if(p) *p = NULLCHAR;
16763       v = StringToVariant(s);
16764       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16765         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16766             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16767                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16768                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16769                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16770             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16771         }
16772         if(p) *p++ = ',';
16773         if(n < 0) return buf;
16774     } while(s = p);
16775     return NULL;
16776 }
16777
16778 int
16779 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16780 {
16781   char buf[MSG_SIZ];
16782   int len = strlen(name);
16783   int val;
16784
16785   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16786     (*p) += len + 1;
16787     sscanf(*p, "%d", &val);
16788     *loc = (val != 0);
16789     while (**p && **p != ' ')
16790       (*p)++;
16791     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16792     SendToProgram(buf, cps);
16793     return TRUE;
16794   }
16795   return FALSE;
16796 }
16797
16798 int
16799 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16800 {
16801   char buf[MSG_SIZ];
16802   int len = strlen(name);
16803   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16804     (*p) += len + 1;
16805     sscanf(*p, "%d", loc);
16806     while (**p && **p != ' ') (*p)++;
16807     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16808     SendToProgram(buf, cps);
16809     return TRUE;
16810   }
16811   return FALSE;
16812 }
16813
16814 int
16815 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16816 {
16817   char buf[MSG_SIZ];
16818   int len = strlen(name);
16819   if (strncmp((*p), name, len) == 0
16820       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16821     (*p) += len + 2;
16822     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16823     sscanf(*p, "%[^\"]", *loc);
16824     while (**p && **p != '\"') (*p)++;
16825     if (**p == '\"') (*p)++;
16826     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16827     SendToProgram(buf, cps);
16828     return TRUE;
16829   }
16830   return FALSE;
16831 }
16832
16833 int
16834 ParseOption (Option *opt, ChessProgramState *cps)
16835 // [HGM] options: process the string that defines an engine option, and determine
16836 // name, type, default value, and allowed value range
16837 {
16838         char *p, *q, buf[MSG_SIZ];
16839         int n, min = (-1)<<31, max = 1<<31, def;
16840
16841         if(p = strstr(opt->name, " -spin ")) {
16842             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16843             if(max < min) max = min; // enforce consistency
16844             if(def < min) def = min;
16845             if(def > max) def = max;
16846             opt->value = def;
16847             opt->min = min;
16848             opt->max = max;
16849             opt->type = Spin;
16850         } else if((p = strstr(opt->name, " -slider "))) {
16851             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16852             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16853             if(max < min) max = min; // enforce consistency
16854             if(def < min) def = min;
16855             if(def > max) def = max;
16856             opt->value = def;
16857             opt->min = min;
16858             opt->max = max;
16859             opt->type = Spin; // Slider;
16860         } else if((p = strstr(opt->name, " -string "))) {
16861             opt->textValue = p+9;
16862             opt->type = TextBox;
16863         } else if((p = strstr(opt->name, " -file "))) {
16864             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16865             opt->textValue = p+7;
16866             opt->type = FileName; // FileName;
16867         } else if((p = strstr(opt->name, " -path "))) {
16868             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16869             opt->textValue = p+7;
16870             opt->type = PathName; // PathName;
16871         } else if(p = strstr(opt->name, " -check ")) {
16872             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16873             opt->value = (def != 0);
16874             opt->type = CheckBox;
16875         } else if(p = strstr(opt->name, " -combo ")) {
16876             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16877             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16878             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16879             opt->value = n = 0;
16880             while(q = StrStr(q, " /// ")) {
16881                 n++; *q = 0;    // count choices, and null-terminate each of them
16882                 q += 5;
16883                 if(*q == '*') { // remember default, which is marked with * prefix
16884                     q++;
16885                     opt->value = n;
16886                 }
16887                 cps->comboList[cps->comboCnt++] = q;
16888             }
16889             cps->comboList[cps->comboCnt++] = NULL;
16890             opt->max = n + 1;
16891             opt->type = ComboBox;
16892         } else if(p = strstr(opt->name, " -button")) {
16893             opt->type = Button;
16894         } else if(p = strstr(opt->name, " -save")) {
16895             opt->type = SaveButton;
16896         } else return FALSE;
16897         *p = 0; // terminate option name
16898         // now look if the command-line options define a setting for this engine option.
16899         if(cps->optionSettings && cps->optionSettings[0])
16900             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16901         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16902           snprintf(buf, MSG_SIZ, "option %s", p);
16903                 if(p = strstr(buf, ",")) *p = 0;
16904                 if(q = strchr(buf, '=')) switch(opt->type) {
16905                     case ComboBox:
16906                         for(n=0; n<opt->max; n++)
16907                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16908                         break;
16909                     case TextBox:
16910                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16911                         break;
16912                     case Spin:
16913                     case CheckBox:
16914                         opt->value = atoi(q+1);
16915                     default:
16916                         break;
16917                 }
16918                 strcat(buf, "\n");
16919                 SendToProgram(buf, cps);
16920         }
16921         return TRUE;
16922 }
16923
16924 void
16925 FeatureDone (ChessProgramState *cps, int val)
16926 {
16927   DelayedEventCallback cb = GetDelayedEvent();
16928   if ((cb == InitBackEnd3 && cps == &first) ||
16929       (cb == SettingsMenuIfReady && cps == &second) ||
16930       (cb == LoadEngine) ||
16931       (cb == TwoMachinesEventIfReady)) {
16932     CancelDelayedEvent();
16933     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16934   }
16935   cps->initDone = val;
16936   if(val) cps->reload = FALSE;
16937 }
16938
16939 /* Parse feature command from engine */
16940 void
16941 ParseFeatures (char *args, ChessProgramState *cps)
16942 {
16943   char *p = args;
16944   char *q = NULL;
16945   int val;
16946   char buf[MSG_SIZ];
16947
16948   for (;;) {
16949     while (*p == ' ') p++;
16950     if (*p == NULLCHAR) return;
16951
16952     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16953     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16954     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16955     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16956     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16957     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16958     if (BoolFeature(&p, "reuse", &val, cps)) {
16959       /* Engine can disable reuse, but can't enable it if user said no */
16960       if (!val) cps->reuse = FALSE;
16961       continue;
16962     }
16963     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16964     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16965       if (gameMode == TwoMachinesPlay) {
16966         DisplayTwoMachinesTitle();
16967       } else {
16968         DisplayTitle("");
16969       }
16970       continue;
16971     }
16972     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16973     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16974     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16975     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16976     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16977     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16978     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16979     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16980     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16981     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16982     if (IntFeature(&p, "done", &val, cps)) {
16983       FeatureDone(cps, val);
16984       continue;
16985     }
16986     /* Added by Tord: */
16987     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16988     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16989     /* End of additions by Tord */
16990
16991     /* [HGM] added features: */
16992     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16993     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16994     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16995     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16996     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16997     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16998     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16999     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17000         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17001         FREE(cps->option[cps->nrOptions].name);
17002         cps->option[cps->nrOptions].name = q; q = NULL;
17003         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17004           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17005             SendToProgram(buf, cps);
17006             continue;
17007         }
17008         if(cps->nrOptions >= MAX_OPTIONS) {
17009             cps->nrOptions--;
17010             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17011             DisplayError(buf, 0);
17012         }
17013         continue;
17014     }
17015     /* End of additions by HGM */
17016
17017     /* unknown feature: complain and skip */
17018     q = p;
17019     while (*q && *q != '=') q++;
17020     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17021     SendToProgram(buf, cps);
17022     p = q;
17023     if (*p == '=') {
17024       p++;
17025       if (*p == '\"') {
17026         p++;
17027         while (*p && *p != '\"') p++;
17028         if (*p == '\"') p++;
17029       } else {
17030         while (*p && *p != ' ') p++;
17031       }
17032     }
17033   }
17034
17035 }
17036
17037 void
17038 PeriodicUpdatesEvent (int newState)
17039 {
17040     if (newState == appData.periodicUpdates)
17041       return;
17042
17043     appData.periodicUpdates=newState;
17044
17045     /* Display type changes, so update it now */
17046 //    DisplayAnalysis();
17047
17048     /* Get the ball rolling again... */
17049     if (newState) {
17050         AnalysisPeriodicEvent(1);
17051         StartAnalysisClock();
17052     }
17053 }
17054
17055 void
17056 PonderNextMoveEvent (int newState)
17057 {
17058     if (newState == appData.ponderNextMove) return;
17059     if (gameMode == EditPosition) EditPositionDone(TRUE);
17060     if (newState) {
17061         SendToProgram("hard\n", &first);
17062         if (gameMode == TwoMachinesPlay) {
17063             SendToProgram("hard\n", &second);
17064         }
17065     } else {
17066         SendToProgram("easy\n", &first);
17067         thinkOutput[0] = NULLCHAR;
17068         if (gameMode == TwoMachinesPlay) {
17069             SendToProgram("easy\n", &second);
17070         }
17071     }
17072     appData.ponderNextMove = newState;
17073 }
17074
17075 void
17076 NewSettingEvent (int option, int *feature, char *command, int value)
17077 {
17078     char buf[MSG_SIZ];
17079
17080     if (gameMode == EditPosition) EditPositionDone(TRUE);
17081     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17082     if(feature == NULL || *feature) SendToProgram(buf, &first);
17083     if (gameMode == TwoMachinesPlay) {
17084         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17085     }
17086 }
17087
17088 void
17089 ShowThinkingEvent ()
17090 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17091 {
17092     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17093     int newState = appData.showThinking
17094         // [HGM] thinking: other features now need thinking output as well
17095         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17096
17097     if (oldState == newState) return;
17098     oldState = newState;
17099     if (gameMode == EditPosition) EditPositionDone(TRUE);
17100     if (oldState) {
17101         SendToProgram("post\n", &first);
17102         if (gameMode == TwoMachinesPlay) {
17103             SendToProgram("post\n", &second);
17104         }
17105     } else {
17106         SendToProgram("nopost\n", &first);
17107         thinkOutput[0] = NULLCHAR;
17108         if (gameMode == TwoMachinesPlay) {
17109             SendToProgram("nopost\n", &second);
17110         }
17111     }
17112 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17113 }
17114
17115 void
17116 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17117 {
17118   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17119   if (pr == NoProc) return;
17120   AskQuestion(title, question, replyPrefix, pr);
17121 }
17122
17123 void
17124 TypeInEvent (char firstChar)
17125 {
17126     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17127         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17128         gameMode == AnalyzeMode || gameMode == EditGame ||
17129         gameMode == EditPosition || gameMode == IcsExamining ||
17130         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17131         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17132                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17133                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17134         gameMode == Training) PopUpMoveDialog(firstChar);
17135 }
17136
17137 void
17138 TypeInDoneEvent (char *move)
17139 {
17140         Board board;
17141         int n, fromX, fromY, toX, toY;
17142         char promoChar;
17143         ChessMove moveType;
17144
17145         // [HGM] FENedit
17146         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17147                 EditPositionPasteFEN(move);
17148                 return;
17149         }
17150         // [HGM] movenum: allow move number to be typed in any mode
17151         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17152           ToNrEvent(2*n-1);
17153           return;
17154         }
17155         // undocumented kludge: allow command-line option to be typed in!
17156         // (potentially fatal, and does not implement the effect of the option.)
17157         // should only be used for options that are values on which future decisions will be made,
17158         // and definitely not on options that would be used during initialization.
17159         if(strstr(move, "!!! -") == move) {
17160             ParseArgsFromString(move+4);
17161             return;
17162         }
17163
17164       if (gameMode != EditGame && currentMove != forwardMostMove &&
17165         gameMode != Training) {
17166         DisplayMoveError(_("Displayed move is not current"));
17167       } else {
17168         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17169           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17170         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17171         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17172           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17173           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17174         } else {
17175           DisplayMoveError(_("Could not parse move"));
17176         }
17177       }
17178 }
17179
17180 void
17181 DisplayMove (int moveNumber)
17182 {
17183     char message[MSG_SIZ];
17184     char res[MSG_SIZ];
17185     char cpThinkOutput[MSG_SIZ];
17186
17187     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17188
17189     if (moveNumber == forwardMostMove - 1 ||
17190         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17191
17192         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17193
17194         if (strchr(cpThinkOutput, '\n')) {
17195             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17196         }
17197     } else {
17198         *cpThinkOutput = NULLCHAR;
17199     }
17200
17201     /* [AS] Hide thinking from human user */
17202     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17203         *cpThinkOutput = NULLCHAR;
17204         if( thinkOutput[0] != NULLCHAR ) {
17205             int i;
17206
17207             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17208                 cpThinkOutput[i] = '.';
17209             }
17210             cpThinkOutput[i] = NULLCHAR;
17211             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17212         }
17213     }
17214
17215     if (moveNumber == forwardMostMove - 1 &&
17216         gameInfo.resultDetails != NULL) {
17217         if (gameInfo.resultDetails[0] == NULLCHAR) {
17218           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17219         } else {
17220           snprintf(res, MSG_SIZ, " {%s} %s",
17221                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17222         }
17223     } else {
17224         res[0] = NULLCHAR;
17225     }
17226
17227     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17228         DisplayMessage(res, cpThinkOutput);
17229     } else {
17230       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17231                 WhiteOnMove(moveNumber) ? " " : ".. ",
17232                 parseList[moveNumber], res);
17233         DisplayMessage(message, cpThinkOutput);
17234     }
17235 }
17236
17237 void
17238 DisplayComment (int moveNumber, char *text)
17239 {
17240     char title[MSG_SIZ];
17241
17242     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17243       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17244     } else {
17245       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17246               WhiteOnMove(moveNumber) ? " " : ".. ",
17247               parseList[moveNumber]);
17248     }
17249     if (text != NULL && (appData.autoDisplayComment || commentUp))
17250         CommentPopUp(title, text);
17251 }
17252
17253 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17254  * might be busy thinking or pondering.  It can be omitted if your
17255  * gnuchess is configured to stop thinking immediately on any user
17256  * input.  However, that gnuchess feature depends on the FIONREAD
17257  * ioctl, which does not work properly on some flavors of Unix.
17258  */
17259 void
17260 Attention (ChessProgramState *cps)
17261 {
17262 #if ATTENTION
17263     if (!cps->useSigint) return;
17264     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17265     switch (gameMode) {
17266       case MachinePlaysWhite:
17267       case MachinePlaysBlack:
17268       case TwoMachinesPlay:
17269       case IcsPlayingWhite:
17270       case IcsPlayingBlack:
17271       case AnalyzeMode:
17272       case AnalyzeFile:
17273         /* Skip if we know it isn't thinking */
17274         if (!cps->maybeThinking) return;
17275         if (appData.debugMode)
17276           fprintf(debugFP, "Interrupting %s\n", cps->which);
17277         InterruptChildProcess(cps->pr);
17278         cps->maybeThinking = FALSE;
17279         break;
17280       default:
17281         break;
17282     }
17283 #endif /*ATTENTION*/
17284 }
17285
17286 int
17287 CheckFlags ()
17288 {
17289     if (whiteTimeRemaining <= 0) {
17290         if (!whiteFlag) {
17291             whiteFlag = TRUE;
17292             if (appData.icsActive) {
17293                 if (appData.autoCallFlag &&
17294                     gameMode == IcsPlayingBlack && !blackFlag) {
17295                   SendToICS(ics_prefix);
17296                   SendToICS("flag\n");
17297                 }
17298             } else {
17299                 if (blackFlag) {
17300                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17301                 } else {
17302                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17303                     if (appData.autoCallFlag) {
17304                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17305                         return TRUE;
17306                     }
17307                 }
17308             }
17309         }
17310     }
17311     if (blackTimeRemaining <= 0) {
17312         if (!blackFlag) {
17313             blackFlag = TRUE;
17314             if (appData.icsActive) {
17315                 if (appData.autoCallFlag &&
17316                     gameMode == IcsPlayingWhite && !whiteFlag) {
17317                   SendToICS(ics_prefix);
17318                   SendToICS("flag\n");
17319                 }
17320             } else {
17321                 if (whiteFlag) {
17322                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17323                 } else {
17324                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17325                     if (appData.autoCallFlag) {
17326                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17327                         return TRUE;
17328                     }
17329                 }
17330             }
17331         }
17332     }
17333     return FALSE;
17334 }
17335
17336 void
17337 CheckTimeControl ()
17338 {
17339     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17340         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17341
17342     /*
17343      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17344      */
17345     if ( !WhiteOnMove(forwardMostMove) ) {
17346         /* White made time control */
17347         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17348         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17349         /* [HGM] time odds: correct new time quota for time odds! */
17350                                             / WhitePlayer()->timeOdds;
17351         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17352     } else {
17353         lastBlack -= blackTimeRemaining;
17354         /* Black made time control */
17355         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17356                                             / WhitePlayer()->other->timeOdds;
17357         lastWhite = whiteTimeRemaining;
17358     }
17359 }
17360
17361 void
17362 DisplayBothClocks ()
17363 {
17364     int wom = gameMode == EditPosition ?
17365       !blackPlaysFirst : WhiteOnMove(currentMove);
17366     DisplayWhiteClock(whiteTimeRemaining, wom);
17367     DisplayBlackClock(blackTimeRemaining, !wom);
17368 }
17369
17370
17371 /* Timekeeping seems to be a portability nightmare.  I think everyone
17372    has ftime(), but I'm really not sure, so I'm including some ifdefs
17373    to use other calls if you don't.  Clocks will be less accurate if
17374    you have neither ftime nor gettimeofday.
17375 */
17376
17377 /* VS 2008 requires the #include outside of the function */
17378 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17379 #include <sys/timeb.h>
17380 #endif
17381
17382 /* Get the current time as a TimeMark */
17383 void
17384 GetTimeMark (TimeMark *tm)
17385 {
17386 #if HAVE_GETTIMEOFDAY
17387
17388     struct timeval timeVal;
17389     struct timezone timeZone;
17390
17391     gettimeofday(&timeVal, &timeZone);
17392     tm->sec = (long) timeVal.tv_sec;
17393     tm->ms = (int) (timeVal.tv_usec / 1000L);
17394
17395 #else /*!HAVE_GETTIMEOFDAY*/
17396 #if HAVE_FTIME
17397
17398 // include <sys/timeb.h> / moved to just above start of function
17399     struct timeb timeB;
17400
17401     ftime(&timeB);
17402     tm->sec = (long) timeB.time;
17403     tm->ms = (int) timeB.millitm;
17404
17405 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17406     tm->sec = (long) time(NULL);
17407     tm->ms = 0;
17408 #endif
17409 #endif
17410 }
17411
17412 /* Return the difference in milliseconds between two
17413    time marks.  We assume the difference will fit in a long!
17414 */
17415 long
17416 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17417 {
17418     return 1000L*(tm2->sec - tm1->sec) +
17419            (long) (tm2->ms - tm1->ms);
17420 }
17421
17422
17423 /*
17424  * Code to manage the game clocks.
17425  *
17426  * In tournament play, black starts the clock and then white makes a move.
17427  * We give the human user a slight advantage if he is playing white---the
17428  * clocks don't run until he makes his first move, so it takes zero time.
17429  * Also, we don't account for network lag, so we could get out of sync
17430  * with GNU Chess's clock -- but then, referees are always right.
17431  */
17432
17433 static TimeMark tickStartTM;
17434 static long intendedTickLength;
17435
17436 long
17437 NextTickLength (long timeRemaining)
17438 {
17439     long nominalTickLength, nextTickLength;
17440
17441     if (timeRemaining > 0L && timeRemaining <= 10000L)
17442       nominalTickLength = 100L;
17443     else
17444       nominalTickLength = 1000L;
17445     nextTickLength = timeRemaining % nominalTickLength;
17446     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17447
17448     return nextTickLength;
17449 }
17450
17451 /* Adjust clock one minute up or down */
17452 void
17453 AdjustClock (Boolean which, int dir)
17454 {
17455     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17456     if(which) blackTimeRemaining += 60000*dir;
17457     else      whiteTimeRemaining += 60000*dir;
17458     DisplayBothClocks();
17459     adjustedClock = TRUE;
17460 }
17461
17462 /* Stop clocks and reset to a fresh time control */
17463 void
17464 ResetClocks ()
17465 {
17466     (void) StopClockTimer();
17467     if (appData.icsActive) {
17468         whiteTimeRemaining = blackTimeRemaining = 0;
17469     } else if (searchTime) {
17470         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17471         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17472     } else { /* [HGM] correct new time quote for time odds */
17473         whiteTC = blackTC = fullTimeControlString;
17474         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17475         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17476     }
17477     if (whiteFlag || blackFlag) {
17478         DisplayTitle("");
17479         whiteFlag = blackFlag = FALSE;
17480     }
17481     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17482     DisplayBothClocks();
17483     adjustedClock = FALSE;
17484 }
17485
17486 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17487
17488 /* Decrement running clock by amount of time that has passed */
17489 void
17490 DecrementClocks ()
17491 {
17492     long timeRemaining;
17493     long lastTickLength, fudge;
17494     TimeMark now;
17495
17496     if (!appData.clockMode) return;
17497     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17498
17499     GetTimeMark(&now);
17500
17501     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17502
17503     /* Fudge if we woke up a little too soon */
17504     fudge = intendedTickLength - lastTickLength;
17505     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17506
17507     if (WhiteOnMove(forwardMostMove)) {
17508         if(whiteNPS >= 0) lastTickLength = 0;
17509         timeRemaining = whiteTimeRemaining -= lastTickLength;
17510         if(timeRemaining < 0 && !appData.icsActive) {
17511             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17512             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17513                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17514                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17515             }
17516         }
17517         DisplayWhiteClock(whiteTimeRemaining - fudge,
17518                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17519     } else {
17520         if(blackNPS >= 0) lastTickLength = 0;
17521         timeRemaining = blackTimeRemaining -= lastTickLength;
17522         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17523             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17524             if(suddenDeath) {
17525                 blackStartMove = forwardMostMove;
17526                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17527             }
17528         }
17529         DisplayBlackClock(blackTimeRemaining - fudge,
17530                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17531     }
17532     if (CheckFlags()) return;
17533
17534     if(twoBoards) { // count down secondary board's clocks as well
17535         activePartnerTime -= lastTickLength;
17536         partnerUp = 1;
17537         if(activePartner == 'W')
17538             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17539         else
17540             DisplayBlackClock(activePartnerTime, TRUE);
17541         partnerUp = 0;
17542     }
17543
17544     tickStartTM = now;
17545     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17546     StartClockTimer(intendedTickLength);
17547
17548     /* if the time remaining has fallen below the alarm threshold, sound the
17549      * alarm. if the alarm has sounded and (due to a takeback or time control
17550      * with increment) the time remaining has increased to a level above the
17551      * threshold, reset the alarm so it can sound again.
17552      */
17553
17554     if (appData.icsActive && appData.icsAlarm) {
17555
17556         /* make sure we are dealing with the user's clock */
17557         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17558                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17559            )) return;
17560
17561         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17562             alarmSounded = FALSE;
17563         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17564             PlayAlarmSound();
17565             alarmSounded = TRUE;
17566         }
17567     }
17568 }
17569
17570
17571 /* A player has just moved, so stop the previously running
17572    clock and (if in clock mode) start the other one.
17573    We redisplay both clocks in case we're in ICS mode, because
17574    ICS gives us an update to both clocks after every move.
17575    Note that this routine is called *after* forwardMostMove
17576    is updated, so the last fractional tick must be subtracted
17577    from the color that is *not* on move now.
17578 */
17579 void
17580 SwitchClocks (int newMoveNr)
17581 {
17582     long lastTickLength;
17583     TimeMark now;
17584     int flagged = FALSE;
17585
17586     GetTimeMark(&now);
17587
17588     if (StopClockTimer() && appData.clockMode) {
17589         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17590         if (!WhiteOnMove(forwardMostMove)) {
17591             if(blackNPS >= 0) lastTickLength = 0;
17592             blackTimeRemaining -= lastTickLength;
17593            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17594 //         if(pvInfoList[forwardMostMove].time == -1)
17595                  pvInfoList[forwardMostMove].time =               // use GUI time
17596                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17597         } else {
17598            if(whiteNPS >= 0) lastTickLength = 0;
17599            whiteTimeRemaining -= lastTickLength;
17600            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17601 //         if(pvInfoList[forwardMostMove].time == -1)
17602                  pvInfoList[forwardMostMove].time =
17603                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17604         }
17605         flagged = CheckFlags();
17606     }
17607     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17608     CheckTimeControl();
17609
17610     if (flagged || !appData.clockMode) return;
17611
17612     switch (gameMode) {
17613       case MachinePlaysBlack:
17614       case MachinePlaysWhite:
17615       case BeginningOfGame:
17616         if (pausing) return;
17617         break;
17618
17619       case EditGame:
17620       case PlayFromGameFile:
17621       case IcsExamining:
17622         return;
17623
17624       default:
17625         break;
17626     }
17627
17628     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17629         if(WhiteOnMove(forwardMostMove))
17630              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17631         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17632     }
17633
17634     tickStartTM = now;
17635     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17636       whiteTimeRemaining : blackTimeRemaining);
17637     StartClockTimer(intendedTickLength);
17638 }
17639
17640
17641 /* Stop both clocks */
17642 void
17643 StopClocks ()
17644 {
17645     long lastTickLength;
17646     TimeMark now;
17647
17648     if (!StopClockTimer()) return;
17649     if (!appData.clockMode) return;
17650
17651     GetTimeMark(&now);
17652
17653     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17654     if (WhiteOnMove(forwardMostMove)) {
17655         if(whiteNPS >= 0) lastTickLength = 0;
17656         whiteTimeRemaining -= lastTickLength;
17657         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17658     } else {
17659         if(blackNPS >= 0) lastTickLength = 0;
17660         blackTimeRemaining -= lastTickLength;
17661         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17662     }
17663     CheckFlags();
17664 }
17665
17666 /* Start clock of player on move.  Time may have been reset, so
17667    if clock is already running, stop and restart it. */
17668 void
17669 StartClocks ()
17670 {
17671     (void) StopClockTimer(); /* in case it was running already */
17672     DisplayBothClocks();
17673     if (CheckFlags()) return;
17674
17675     if (!appData.clockMode) return;
17676     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17677
17678     GetTimeMark(&tickStartTM);
17679     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17680       whiteTimeRemaining : blackTimeRemaining);
17681
17682    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17683     whiteNPS = blackNPS = -1;
17684     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17685        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17686         whiteNPS = first.nps;
17687     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17688        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17689         blackNPS = first.nps;
17690     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17691         whiteNPS = second.nps;
17692     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17693         blackNPS = second.nps;
17694     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17695
17696     StartClockTimer(intendedTickLength);
17697 }
17698
17699 char *
17700 TimeString (long ms)
17701 {
17702     long second, minute, hour, day;
17703     char *sign = "";
17704     static char buf[32];
17705
17706     if (ms > 0 && ms <= 9900) {
17707       /* convert milliseconds to tenths, rounding up */
17708       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17709
17710       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17711       return buf;
17712     }
17713
17714     /* convert milliseconds to seconds, rounding up */
17715     /* use floating point to avoid strangeness of integer division
17716        with negative dividends on many machines */
17717     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17718
17719     if (second < 0) {
17720         sign = "-";
17721         second = -second;
17722     }
17723
17724     day = second / (60 * 60 * 24);
17725     second = second % (60 * 60 * 24);
17726     hour = second / (60 * 60);
17727     second = second % (60 * 60);
17728     minute = second / 60;
17729     second = second % 60;
17730
17731     if (day > 0)
17732       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17733               sign, day, hour, minute, second);
17734     else if (hour > 0)
17735       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17736     else
17737       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17738
17739     return buf;
17740 }
17741
17742
17743 /*
17744  * This is necessary because some C libraries aren't ANSI C compliant yet.
17745  */
17746 char *
17747 StrStr (char *string, char *match)
17748 {
17749     int i, length;
17750
17751     length = strlen(match);
17752
17753     for (i = strlen(string) - length; i >= 0; i--, string++)
17754       if (!strncmp(match, string, length))
17755         return string;
17756
17757     return NULL;
17758 }
17759
17760 char *
17761 StrCaseStr (char *string, char *match)
17762 {
17763     int i, j, length;
17764
17765     length = strlen(match);
17766
17767     for (i = strlen(string) - length; i >= 0; i--, string++) {
17768         for (j = 0; j < length; j++) {
17769             if (ToLower(match[j]) != ToLower(string[j]))
17770               break;
17771         }
17772         if (j == length) return string;
17773     }
17774
17775     return NULL;
17776 }
17777
17778 #ifndef _amigados
17779 int
17780 StrCaseCmp (char *s1, char *s2)
17781 {
17782     char c1, c2;
17783
17784     for (;;) {
17785         c1 = ToLower(*s1++);
17786         c2 = ToLower(*s2++);
17787         if (c1 > c2) return 1;
17788         if (c1 < c2) return -1;
17789         if (c1 == NULLCHAR) return 0;
17790     }
17791 }
17792
17793
17794 int
17795 ToLower (int c)
17796 {
17797     return isupper(c) ? tolower(c) : c;
17798 }
17799
17800
17801 int
17802 ToUpper (int c)
17803 {
17804     return islower(c) ? toupper(c) : c;
17805 }
17806 #endif /* !_amigados    */
17807
17808 char *
17809 StrSave (char *s)
17810 {
17811   char *ret;
17812
17813   if ((ret = (char *) malloc(strlen(s) + 1)))
17814     {
17815       safeStrCpy(ret, s, strlen(s)+1);
17816     }
17817   return ret;
17818 }
17819
17820 char *
17821 StrSavePtr (char *s, char **savePtr)
17822 {
17823     if (*savePtr) {
17824         free(*savePtr);
17825     }
17826     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17827       safeStrCpy(*savePtr, s, strlen(s)+1);
17828     }
17829     return(*savePtr);
17830 }
17831
17832 char *
17833 PGNDate ()
17834 {
17835     time_t clock;
17836     struct tm *tm;
17837     char buf[MSG_SIZ];
17838
17839     clock = time((time_t *)NULL);
17840     tm = localtime(&clock);
17841     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17842             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17843     return StrSave(buf);
17844 }
17845
17846
17847 char *
17848 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17849 {
17850     int i, j, fromX, fromY, toX, toY;
17851     int whiteToPlay;
17852     char buf[MSG_SIZ];
17853     char *p, *q;
17854     int emptycount;
17855     ChessSquare piece;
17856
17857     whiteToPlay = (gameMode == EditPosition) ?
17858       !blackPlaysFirst : (move % 2 == 0);
17859     p = buf;
17860
17861     /* Piece placement data */
17862     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17863         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17864         emptycount = 0;
17865         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17866             if (boards[move][i][j] == EmptySquare) {
17867                 emptycount++;
17868             } else { ChessSquare piece = boards[move][i][j];
17869                 if (emptycount > 0) {
17870                     if(emptycount<10) /* [HGM] can be >= 10 */
17871                         *p++ = '0' + emptycount;
17872                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17873                     emptycount = 0;
17874                 }
17875                 if(PieceToChar(piece) == '+') {
17876                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17877                     *p++ = '+';
17878                     piece = (ChessSquare)(CHUDEMOTED piece);
17879                 }
17880                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17881                 if(*p = PieceSuffix(piece)) p++;
17882                 if(p[-1] == '~') {
17883                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17884                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17885                     *p++ = '~';
17886                 }
17887             }
17888         }
17889         if (emptycount > 0) {
17890             if(emptycount<10) /* [HGM] can be >= 10 */
17891                 *p++ = '0' + emptycount;
17892             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17893             emptycount = 0;
17894         }
17895         *p++ = '/';
17896     }
17897     *(p - 1) = ' ';
17898
17899     /* [HGM] print Crazyhouse or Shogi holdings */
17900     if( gameInfo.holdingsWidth ) {
17901         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17902         q = p;
17903         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17904             piece = boards[move][i][BOARD_WIDTH-1];
17905             if( piece != EmptySquare )
17906               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17907                   *p++ = PieceToChar(piece);
17908         }
17909         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17910             piece = boards[move][BOARD_HEIGHT-i-1][0];
17911             if( piece != EmptySquare )
17912               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17913                   *p++ = PieceToChar(piece);
17914         }
17915
17916         if( q == p ) *p++ = '-';
17917         *p++ = ']';
17918         *p++ = ' ';
17919     }
17920
17921     /* Active color */
17922     *p++ = whiteToPlay ? 'w' : 'b';
17923     *p++ = ' ';
17924
17925   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17926     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17927   } else {
17928   if(nrCastlingRights) {
17929      int handW=0, handB=0;
17930      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
17931         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
17932         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17933      }
17934      q = p;
17935      if(appData.fischerCastling) {
17936         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
17937            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17938                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17939         } else {
17940        /* [HGM] write directly from rights */
17941            if(boards[move][CASTLING][2] != NoRights &&
17942               boards[move][CASTLING][0] != NoRights   )
17943                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17944            if(boards[move][CASTLING][2] != NoRights &&
17945               boards[move][CASTLING][1] != NoRights   )
17946                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17947         }
17948         if(handB) {
17949            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17950                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17951         } else {
17952            if(boards[move][CASTLING][5] != NoRights &&
17953               boards[move][CASTLING][3] != NoRights   )
17954                 *p++ = boards[move][CASTLING][3] + AAA;
17955            if(boards[move][CASTLING][5] != NoRights &&
17956               boards[move][CASTLING][4] != NoRights   )
17957                 *p++ = boards[move][CASTLING][4] + AAA;
17958         }
17959      } else {
17960
17961         /* [HGM] write true castling rights */
17962         if( nrCastlingRights == 6 ) {
17963             int q, k=0;
17964             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17965                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17966             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17967                  boards[move][CASTLING][2] != NoRights  );
17968             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
17969                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17970                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17971                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17972             }
17973             if(q) *p++ = 'Q';
17974             k = 0;
17975             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17976                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17977             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17978                  boards[move][CASTLING][5] != NoRights  );
17979             if(handB) {
17980                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17981                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17982                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17983             }
17984             if(q) *p++ = 'q';
17985         }
17986      }
17987      if (q == p) *p++ = '-'; /* No castling rights */
17988      *p++ = ' ';
17989   }
17990
17991   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17992      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17993      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17994     /* En passant target square */
17995     if (move > backwardMostMove) {
17996         fromX = moveList[move - 1][0] - AAA;
17997         fromY = moveList[move - 1][1] - ONE;
17998         toX = moveList[move - 1][2] - AAA;
17999         toY = moveList[move - 1][3] - ONE;
18000         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18001             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18002             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18003             fromX == toX) {
18004             /* 2-square pawn move just happened */
18005             *p++ = toX + AAA;
18006             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18007         } else {
18008             *p++ = '-';
18009         }
18010     } else if(move == backwardMostMove) {
18011         // [HGM] perhaps we should always do it like this, and forget the above?
18012         if((signed char)boards[move][EP_STATUS] >= 0) {
18013             *p++ = boards[move][EP_STATUS] + AAA;
18014             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18015         } else {
18016             *p++ = '-';
18017         }
18018     } else {
18019         *p++ = '-';
18020     }
18021     *p++ = ' ';
18022   }
18023   }
18024
18025     if(moveCounts)
18026     {   int i = 0, j=move;
18027
18028         /* [HGM] find reversible plies */
18029         if (appData.debugMode) { int k;
18030             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18031             for(k=backwardMostMove; k<=forwardMostMove; k++)
18032                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18033
18034         }
18035
18036         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18037         if( j == backwardMostMove ) i += initialRulePlies;
18038         sprintf(p, "%d ", i);
18039         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18040
18041         /* Fullmove number */
18042         sprintf(p, "%d", (move / 2) + 1);
18043     } else *--p = NULLCHAR;
18044
18045     return StrSave(buf);
18046 }
18047
18048 Boolean
18049 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18050 {
18051     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18052     char *p, c;
18053     int emptycount, virgin[BOARD_FILES];
18054     ChessSquare piece;
18055
18056     p = fen;
18057
18058     /* Piece placement data */
18059     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18060         j = 0;
18061         for (;;) {
18062             if (*p == '/' || *p == ' ' || *p == '[' ) {
18063                 if(j > w) w = j;
18064                 emptycount = gameInfo.boardWidth - j;
18065                 while (emptycount--)
18066                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18067                 if (*p == '/') p++;
18068                 else if(autoSize) { // we stumbled unexpectedly into end of board
18069                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18070                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18071                     }
18072                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18073                 }
18074                 break;
18075 #if(BOARD_FILES >= 10)*0
18076             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18077                 p++; emptycount=10;
18078                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18079                 while (emptycount--)
18080                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18081 #endif
18082             } else if (*p == '*') {
18083                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18084             } else if (isdigit(*p)) {
18085                 emptycount = *p++ - '0';
18086                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18087                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18088                 while (emptycount--)
18089                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18090             } else if (*p == '<') {
18091                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18092                 else if (i != 0 || !shuffle) return FALSE;
18093                 p++;
18094             } else if (shuffle && *p == '>') {
18095                 p++; // for now ignore closing shuffle range, and assume rank-end
18096             } else if (*p == '?') {
18097                 if (j >= gameInfo.boardWidth) return FALSE;
18098                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18099                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18100             } else if (*p == '+' || isalpha(*p)) {
18101                 char *q, *s = SUFFIXES;
18102                 if (j >= gameInfo.boardWidth) return FALSE;
18103                 if(*p=='+') {
18104                     char c = *++p;
18105                     if(q = strchr(s, p[1])) p++;
18106                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18107                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18108                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
18109                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18110                 } else {
18111                     char c = *p++;
18112                     if(q = strchr(s, *p)) p++;
18113                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18114                 }
18115
18116                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18117                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18118                     piece = (ChessSquare) (PROMOTED piece);
18119                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18120                     p++;
18121                 }
18122                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18123                 if(piece == WhiteKing) wKingRank = i;
18124                 if(piece == BlackKing) bKingRank = i;
18125             } else {
18126                 return FALSE;
18127             }
18128         }
18129     }
18130     while (*p == '/' || *p == ' ') p++;
18131
18132     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
18133
18134     /* [HGM] by default clear Crazyhouse holdings, if present */
18135     if(gameInfo.holdingsWidth) {
18136        for(i=0; i<BOARD_HEIGHT; i++) {
18137            board[i][0]             = EmptySquare; /* black holdings */
18138            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18139            board[i][1]             = (ChessSquare) 0; /* black counts */
18140            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18141        }
18142     }
18143
18144     /* [HGM] look for Crazyhouse holdings here */
18145     while(*p==' ') p++;
18146     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18147         int swap=0, wcnt=0, bcnt=0;
18148         if(*p == '[') p++;
18149         if(*p == '<') swap++, p++;
18150         if(*p == '-' ) p++; /* empty holdings */ else {
18151             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18152             /* if we would allow FEN reading to set board size, we would   */
18153             /* have to add holdings and shift the board read so far here   */
18154             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18155                 p++;
18156                 if((int) piece >= (int) BlackPawn ) {
18157                     i = (int)piece - (int)BlackPawn;
18158                     i = PieceToNumber((ChessSquare)i);
18159                     if( i >= gameInfo.holdingsSize ) return FALSE;
18160                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18161                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18162                     bcnt++;
18163                 } else {
18164                     i = (int)piece - (int)WhitePawn;
18165                     i = PieceToNumber((ChessSquare)i);
18166                     if( i >= gameInfo.holdingsSize ) return FALSE;
18167                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18168                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18169                     wcnt++;
18170                 }
18171             }
18172             if(subst) { // substitute back-rank question marks by holdings pieces
18173                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18174                     int k, m, n = bcnt + 1;
18175                     if(board[0][j] == ClearBoard) {
18176                         if(!wcnt) return FALSE;
18177                         n = rand() % wcnt;
18178                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18179                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18180                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18181                             break;
18182                         }
18183                     }
18184                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18185                         if(!bcnt) return FALSE;
18186                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18187                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18188                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18189                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18190                             break;
18191                         }
18192                     }
18193                 }
18194                 subst = 0;
18195             }
18196         }
18197         if(*p == ']') p++;
18198     }
18199
18200     if(subst) return FALSE; // substitution requested, but no holdings
18201
18202     while(*p == ' ') p++;
18203
18204     /* Active color */
18205     c = *p++;
18206     if(appData.colorNickNames) {
18207       if( c == appData.colorNickNames[0] ) c = 'w'; else
18208       if( c == appData.colorNickNames[1] ) c = 'b';
18209     }
18210     switch (c) {
18211       case 'w':
18212         *blackPlaysFirst = FALSE;
18213         break;
18214       case 'b':
18215         *blackPlaysFirst = TRUE;
18216         break;
18217       default:
18218         return FALSE;
18219     }
18220
18221     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18222     /* return the extra info in global variiables             */
18223
18224     while(*p==' ') p++;
18225
18226     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18227         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18228         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18229     }
18230
18231     /* set defaults in case FEN is incomplete */
18232     board[EP_STATUS] = EP_UNKNOWN;
18233     for(i=0; i<nrCastlingRights; i++ ) {
18234         board[CASTLING][i] =
18235             appData.fischerCastling ? NoRights : initialRights[i];
18236     }   /* assume possible unless obviously impossible */
18237     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18238     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18239     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18240                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18241     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18242     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18243     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18244                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18245     FENrulePlies = 0;
18246
18247     if(nrCastlingRights) {
18248       int fischer = 0;
18249       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18250       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18251           /* castling indicator present, so default becomes no castlings */
18252           for(i=0; i<nrCastlingRights; i++ ) {
18253                  board[CASTLING][i] = NoRights;
18254           }
18255       }
18256       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18257              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18258              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18259              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18260         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18261
18262         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18263             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18264             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18265         }
18266         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18267             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18268         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18269                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18270         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18271                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18272         switch(c) {
18273           case'K':
18274               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18275               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18276               board[CASTLING][2] = whiteKingFile;
18277               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18278               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18279               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18280               break;
18281           case'Q':
18282               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18283               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18284               board[CASTLING][2] = whiteKingFile;
18285               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18286               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18287               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18288               break;
18289           case'k':
18290               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18291               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18292               board[CASTLING][5] = blackKingFile;
18293               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18294               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18295               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18296               break;
18297           case'q':
18298               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18299               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18300               board[CASTLING][5] = blackKingFile;
18301               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18302               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18303               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18304           case '-':
18305               break;
18306           default: /* FRC castlings */
18307               if(c >= 'a') { /* black rights */
18308                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18309                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18310                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18311                   if(i == BOARD_RGHT) break;
18312                   board[CASTLING][5] = i;
18313                   c -= AAA;
18314                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18315                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18316                   if(c > i)
18317                       board[CASTLING][3] = c;
18318                   else
18319                       board[CASTLING][4] = c;
18320               } else { /* white rights */
18321                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18322                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18323                     if(board[0][i] == WhiteKing) break;
18324                   if(i == BOARD_RGHT) break;
18325                   board[CASTLING][2] = i;
18326                   c -= AAA - 'a' + 'A';
18327                   if(board[0][c] >= WhiteKing) break;
18328                   if(c > i)
18329                       board[CASTLING][0] = c;
18330                   else
18331                       board[CASTLING][1] = c;
18332               }
18333         }
18334       }
18335       for(i=0; i<nrCastlingRights; i++)
18336         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18337       if(gameInfo.variant == VariantSChess)
18338         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18339       if(fischer && shuffle) appData.fischerCastling = TRUE;
18340     if (appData.debugMode) {
18341         fprintf(debugFP, "FEN castling rights:");
18342         for(i=0; i<nrCastlingRights; i++)
18343         fprintf(debugFP, " %d", board[CASTLING][i]);
18344         fprintf(debugFP, "\n");
18345     }
18346
18347       while(*p==' ') p++;
18348     }
18349
18350     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18351
18352     /* read e.p. field in games that know e.p. capture */
18353     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18354        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18355        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18356       if(*p=='-') {
18357         p++; board[EP_STATUS] = EP_NONE;
18358       } else {
18359          char c = *p++ - AAA;
18360
18361          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18362          if(*p >= '0' && *p <='9') p++;
18363          board[EP_STATUS] = c;
18364       }
18365     }
18366
18367
18368     if(sscanf(p, "%d", &i) == 1) {
18369         FENrulePlies = i; /* 50-move ply counter */
18370         /* (The move number is still ignored)    */
18371     }
18372
18373     return TRUE;
18374 }
18375
18376 void
18377 EditPositionPasteFEN (char *fen)
18378 {
18379   if (fen != NULL) {
18380     Board initial_position;
18381
18382     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18383       DisplayError(_("Bad FEN position in clipboard"), 0);
18384       return ;
18385     } else {
18386       int savedBlackPlaysFirst = blackPlaysFirst;
18387       EditPositionEvent();
18388       blackPlaysFirst = savedBlackPlaysFirst;
18389       CopyBoard(boards[0], initial_position);
18390       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18391       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18392       DisplayBothClocks();
18393       DrawPosition(FALSE, boards[currentMove]);
18394     }
18395   }
18396 }
18397
18398 static char cseq[12] = "\\   ";
18399
18400 Boolean
18401 set_cont_sequence (char *new_seq)
18402 {
18403     int len;
18404     Boolean ret;
18405
18406     // handle bad attempts to set the sequence
18407         if (!new_seq)
18408                 return 0; // acceptable error - no debug
18409
18410     len = strlen(new_seq);
18411     ret = (len > 0) && (len < sizeof(cseq));
18412     if (ret)
18413       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18414     else if (appData.debugMode)
18415       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18416     return ret;
18417 }
18418
18419 /*
18420     reformat a source message so words don't cross the width boundary.  internal
18421     newlines are not removed.  returns the wrapped size (no null character unless
18422     included in source message).  If dest is NULL, only calculate the size required
18423     for the dest buffer.  lp argument indicats line position upon entry, and it's
18424     passed back upon exit.
18425 */
18426 int
18427 wrap (char *dest, char *src, int count, int width, int *lp)
18428 {
18429     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18430
18431     cseq_len = strlen(cseq);
18432     old_line = line = *lp;
18433     ansi = len = clen = 0;
18434
18435     for (i=0; i < count; i++)
18436     {
18437         if (src[i] == '\033')
18438             ansi = 1;
18439
18440         // if we hit the width, back up
18441         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18442         {
18443             // store i & len in case the word is too long
18444             old_i = i, old_len = len;
18445
18446             // find the end of the last word
18447             while (i && src[i] != ' ' && src[i] != '\n')
18448             {
18449                 i--;
18450                 len--;
18451             }
18452
18453             // word too long?  restore i & len before splitting it
18454             if ((old_i-i+clen) >= width)
18455             {
18456                 i = old_i;
18457                 len = old_len;
18458             }
18459
18460             // extra space?
18461             if (i && src[i-1] == ' ')
18462                 len--;
18463
18464             if (src[i] != ' ' && src[i] != '\n')
18465             {
18466                 i--;
18467                 if (len)
18468                     len--;
18469             }
18470
18471             // now append the newline and continuation sequence
18472             if (dest)
18473                 dest[len] = '\n';
18474             len++;
18475             if (dest)
18476                 strncpy(dest+len, cseq, cseq_len);
18477             len += cseq_len;
18478             line = cseq_len;
18479             clen = cseq_len;
18480             continue;
18481         }
18482
18483         if (dest)
18484             dest[len] = src[i];
18485         len++;
18486         if (!ansi)
18487             line++;
18488         if (src[i] == '\n')
18489             line = 0;
18490         if (src[i] == 'm')
18491             ansi = 0;
18492     }
18493     if (dest && appData.debugMode)
18494     {
18495         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18496             count, width, line, len, *lp);
18497         show_bytes(debugFP, src, count);
18498         fprintf(debugFP, "\ndest: ");
18499         show_bytes(debugFP, dest, len);
18500         fprintf(debugFP, "\n");
18501     }
18502     *lp = dest ? line : old_line;
18503
18504     return len;
18505 }
18506
18507 // [HGM] vari: routines for shelving variations
18508 Boolean modeRestore = FALSE;
18509
18510 void
18511 PushInner (int firstMove, int lastMove)
18512 {
18513         int i, j, nrMoves = lastMove - firstMove;
18514
18515         // push current tail of game on stack
18516         savedResult[storedGames] = gameInfo.result;
18517         savedDetails[storedGames] = gameInfo.resultDetails;
18518         gameInfo.resultDetails = NULL;
18519         savedFirst[storedGames] = firstMove;
18520         savedLast [storedGames] = lastMove;
18521         savedFramePtr[storedGames] = framePtr;
18522         framePtr -= nrMoves; // reserve space for the boards
18523         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18524             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18525             for(j=0; j<MOVE_LEN; j++)
18526                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18527             for(j=0; j<2*MOVE_LEN; j++)
18528                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18529             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18530             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18531             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18532             pvInfoList[firstMove+i-1].depth = 0;
18533             commentList[framePtr+i] = commentList[firstMove+i];
18534             commentList[firstMove+i] = NULL;
18535         }
18536
18537         storedGames++;
18538         forwardMostMove = firstMove; // truncate game so we can start variation
18539 }
18540
18541 void
18542 PushTail (int firstMove, int lastMove)
18543 {
18544         if(appData.icsActive) { // only in local mode
18545                 forwardMostMove = currentMove; // mimic old ICS behavior
18546                 return;
18547         }
18548         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18549
18550         PushInner(firstMove, lastMove);
18551         if(storedGames == 1) GreyRevert(FALSE);
18552         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18553 }
18554
18555 void
18556 PopInner (Boolean annotate)
18557 {
18558         int i, j, nrMoves;
18559         char buf[8000], moveBuf[20];
18560
18561         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18562         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18563         nrMoves = savedLast[storedGames] - currentMove;
18564         if(annotate) {
18565                 int cnt = 10;
18566                 if(!WhiteOnMove(currentMove))
18567                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18568                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18569                 for(i=currentMove; i<forwardMostMove; i++) {
18570                         if(WhiteOnMove(i))
18571                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18572                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18573                         strcat(buf, moveBuf);
18574                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18575                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18576                 }
18577                 strcat(buf, ")");
18578         }
18579         for(i=1; i<=nrMoves; i++) { // copy last variation back
18580             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18581             for(j=0; j<MOVE_LEN; j++)
18582                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18583             for(j=0; j<2*MOVE_LEN; j++)
18584                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18585             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18586             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18587             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18588             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18589             commentList[currentMove+i] = commentList[framePtr+i];
18590             commentList[framePtr+i] = NULL;
18591         }
18592         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18593         framePtr = savedFramePtr[storedGames];
18594         gameInfo.result = savedResult[storedGames];
18595         if(gameInfo.resultDetails != NULL) {
18596             free(gameInfo.resultDetails);
18597       }
18598         gameInfo.resultDetails = savedDetails[storedGames];
18599         forwardMostMove = currentMove + nrMoves;
18600 }
18601
18602 Boolean
18603 PopTail (Boolean annotate)
18604 {
18605         if(appData.icsActive) return FALSE; // only in local mode
18606         if(!storedGames) return FALSE; // sanity
18607         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18608
18609         PopInner(annotate);
18610         if(currentMove < forwardMostMove) ForwardEvent(); else
18611         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18612
18613         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18614         return TRUE;
18615 }
18616
18617 void
18618 CleanupTail ()
18619 {       // remove all shelved variations
18620         int i;
18621         for(i=0; i<storedGames; i++) {
18622             if(savedDetails[i])
18623                 free(savedDetails[i]);
18624             savedDetails[i] = NULL;
18625         }
18626         for(i=framePtr; i<MAX_MOVES; i++) {
18627                 if(commentList[i]) free(commentList[i]);
18628                 commentList[i] = NULL;
18629         }
18630         framePtr = MAX_MOVES-1;
18631         storedGames = 0;
18632 }
18633
18634 void
18635 LoadVariation (int index, char *text)
18636 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18637         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18638         int level = 0, move;
18639
18640         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18641         // first find outermost bracketing variation
18642         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18643             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18644                 if(*p == '{') wait = '}'; else
18645                 if(*p == '[') wait = ']'; else
18646                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18647                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18648             }
18649             if(*p == wait) wait = NULLCHAR; // closing ]} found
18650             p++;
18651         }
18652         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18653         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18654         end[1] = NULLCHAR; // clip off comment beyond variation
18655         ToNrEvent(currentMove-1);
18656         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18657         // kludge: use ParsePV() to append variation to game
18658         move = currentMove;
18659         ParsePV(start, TRUE, TRUE);
18660         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18661         ClearPremoveHighlights();
18662         CommentPopDown();
18663         ToNrEvent(currentMove+1);
18664 }
18665
18666 void
18667 LoadTheme ()
18668 {
18669     char *p, *q, buf[MSG_SIZ];
18670     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18671         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18672         ParseArgsFromString(buf);
18673         ActivateTheme(TRUE); // also redo colors
18674         return;
18675     }
18676     p = nickName;
18677     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18678     {
18679         int len;
18680         q = appData.themeNames;
18681         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18682       if(appData.useBitmaps) {
18683         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18684                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18685                 appData.liteBackTextureMode,
18686                 appData.darkBackTextureMode );
18687       } else {
18688         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18689                 Col2Text(2),   // lightSquareColor
18690                 Col2Text(3) ); // darkSquareColor
18691       }
18692       if(appData.useBorder) {
18693         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18694                 appData.border);
18695       } else {
18696         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18697       }
18698       if(appData.useFont) {
18699         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18700                 appData.renderPiecesWithFont,
18701                 appData.fontToPieceTable,
18702                 Col2Text(9),    // appData.fontBackColorWhite
18703                 Col2Text(10) ); // appData.fontForeColorBlack
18704       } else {
18705         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18706                 appData.pieceDirectory);
18707         if(!appData.pieceDirectory[0])
18708           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18709                 Col2Text(0),   // whitePieceColor
18710                 Col2Text(1) ); // blackPieceColor
18711       }
18712       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18713                 Col2Text(4),   // highlightSquareColor
18714                 Col2Text(5) ); // premoveHighlightColor
18715         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18716         if(insert != q) insert[-1] = NULLCHAR;
18717         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18718         if(q)   free(q);
18719     }
18720     ActivateTheme(FALSE);
18721 }