e68b3af7347dec3a490729c8391f3a662ad54580
[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           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5145                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5146                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5147                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5148           SendToProgram(buf, cps);
5149       } else
5150       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5151         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5152           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5153           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5154                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5155         } else
5156           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5157                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5158         SendToProgram(buf, cps);
5159       }
5160       else SendToProgram(moveList[moveNum], cps);
5161       /* End of additions by Tord */
5162     }
5163
5164     /* [HGM] setting up the opening has brought engine in force mode! */
5165     /*       Send 'go' if we are in a mode where machine should play. */
5166     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5167         (gameMode == TwoMachinesPlay   ||
5168 #if ZIPPY
5169          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5170 #endif
5171          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5172         SendToProgram("go\n", cps);
5173   if (appData.debugMode) {
5174     fprintf(debugFP, "(extra)\n");
5175   }
5176     }
5177     setboardSpoiledMachineBlack = 0;
5178 }
5179
5180 void
5181 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5182 {
5183     char user_move[MSG_SIZ];
5184     char suffix[4];
5185
5186     if(gameInfo.variant == VariantSChess && promoChar) {
5187         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5188         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5189     } else suffix[0] = NULLCHAR;
5190
5191     switch (moveType) {
5192       default:
5193         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5194                 (int)moveType, fromX, fromY, toX, toY);
5195         DisplayError(user_move + strlen("say "), 0);
5196         break;
5197       case WhiteKingSideCastle:
5198       case BlackKingSideCastle:
5199       case WhiteQueenSideCastleWild:
5200       case BlackQueenSideCastleWild:
5201       /* PUSH Fabien */
5202       case WhiteHSideCastleFR:
5203       case BlackHSideCastleFR:
5204       /* POP Fabien */
5205         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5206         break;
5207       case WhiteQueenSideCastle:
5208       case BlackQueenSideCastle:
5209       case WhiteKingSideCastleWild:
5210       case BlackKingSideCastleWild:
5211       /* PUSH Fabien */
5212       case WhiteASideCastleFR:
5213       case BlackASideCastleFR:
5214       /* POP Fabien */
5215         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5216         break;
5217       case WhiteNonPromotion:
5218       case BlackNonPromotion:
5219         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5220         break;
5221       case WhitePromotion:
5222       case BlackPromotion:
5223         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5224            gameInfo.variant == VariantMakruk)
5225           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5226                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5227                 PieceToChar(WhiteFerz));
5228         else if(gameInfo.variant == VariantGreat)
5229           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5230                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5231                 PieceToChar(WhiteMan));
5232         else
5233           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5234                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5235                 promoChar);
5236         break;
5237       case WhiteDrop:
5238       case BlackDrop:
5239       drop:
5240         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5241                  ToUpper(PieceToChar((ChessSquare) fromX)),
5242                  AAA + toX, ONE + toY);
5243         break;
5244       case IllegalMove:  /* could be a variant we don't quite understand */
5245         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5246       case NormalMove:
5247       case WhiteCapturesEnPassant:
5248       case BlackCapturesEnPassant:
5249         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5250                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5251         break;
5252     }
5253     SendToICS(user_move);
5254     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5255         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5256 }
5257
5258 void
5259 UploadGameEvent ()
5260 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5261     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5262     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5263     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5264       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5265       return;
5266     }
5267     if(gameMode != IcsExamining) { // is this ever not the case?
5268         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5269
5270         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5271           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5272         } else { // on FICS we must first go to general examine mode
5273           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5274         }
5275         if(gameInfo.variant != VariantNormal) {
5276             // try figure out wild number, as xboard names are not always valid on ICS
5277             for(i=1; i<=36; i++) {
5278               snprintf(buf, MSG_SIZ, "wild/%d", i);
5279                 if(StringToVariant(buf) == gameInfo.variant) break;
5280             }
5281             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5282             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5283             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5284         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5285         SendToICS(ics_prefix);
5286         SendToICS(buf);
5287         if(startedFromSetupPosition || backwardMostMove != 0) {
5288           fen = PositionToFEN(backwardMostMove, NULL, 1);
5289           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5290             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5291             SendToICS(buf);
5292           } else { // FICS: everything has to set by separate bsetup commands
5293             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5294             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5295             SendToICS(buf);
5296             if(!WhiteOnMove(backwardMostMove)) {
5297                 SendToICS("bsetup tomove black\n");
5298             }
5299             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5300             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5301             SendToICS(buf);
5302             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5303             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5304             SendToICS(buf);
5305             i = boards[backwardMostMove][EP_STATUS];
5306             if(i >= 0) { // set e.p.
5307               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5308                 SendToICS(buf);
5309             }
5310             bsetup++;
5311           }
5312         }
5313       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5314             SendToICS("bsetup done\n"); // switch to normal examining.
5315     }
5316     for(i = backwardMostMove; i<last; i++) {
5317         char buf[20];
5318         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5319         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5320             int len = strlen(moveList[i]);
5321             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5322             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5323         }
5324         SendToICS(buf);
5325     }
5326     SendToICS(ics_prefix);
5327     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5328 }
5329
5330 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5331 int legNr = 1;
5332
5333 void
5334 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5335 {
5336     if (rf == DROP_RANK) {
5337       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5338       sprintf(move, "%c@%c%c\n",
5339                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5340     } else {
5341         if (promoChar == 'x' || promoChar == NULLCHAR) {
5342           sprintf(move, "%c%c%c%c\n",
5343                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5344           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5345         } else {
5346             sprintf(move, "%c%c%c%c%c\n",
5347                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5348         }
5349     }
5350 }
5351
5352 void
5353 ProcessICSInitScript (FILE *f)
5354 {
5355     char buf[MSG_SIZ];
5356
5357     while (fgets(buf, MSG_SIZ, f)) {
5358         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5359     }
5360
5361     fclose(f);
5362 }
5363
5364
5365 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5366 int dragging;
5367 static ClickType lastClickType;
5368
5369 int
5370 Partner (ChessSquare *p)
5371 { // change piece into promotion partner if one shogi-promotes to the other
5372   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5373   ChessSquare partner;
5374   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5375   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5376   *p = partner;
5377   return 1;
5378 }
5379
5380 void
5381 Sweep (int step)
5382 {
5383     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5384     static int toggleFlag;
5385     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5386     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5387     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5388     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5389     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5390     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5391     do {
5392         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5393         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5394         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5395         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5396         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5397         if(!step) step = -1;
5398     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5399             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5400             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5401             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5402     if(toX >= 0) {
5403         int victim = boards[currentMove][toY][toX];
5404         boards[currentMove][toY][toX] = promoSweep;
5405         DrawPosition(FALSE, boards[currentMove]);
5406         boards[currentMove][toY][toX] = victim;
5407     } else
5408     ChangeDragPiece(promoSweep);
5409 }
5410
5411 int
5412 PromoScroll (int x, int y)
5413 {
5414   int step = 0;
5415
5416   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5417   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5418   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5419   if(!step) return FALSE;
5420   lastX = x; lastY = y;
5421   if((promoSweep < BlackPawn) == flipView) step = -step;
5422   if(step > 0) selectFlag = 1;
5423   if(!selectFlag) Sweep(step);
5424   return FALSE;
5425 }
5426
5427 void
5428 NextPiece (int step)
5429 {
5430     ChessSquare piece = boards[currentMove][toY][toX];
5431     do {
5432         pieceSweep -= step;
5433         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5434         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5435         if(!step) step = -1;
5436     } while(PieceToChar(pieceSweep) == '.');
5437     boards[currentMove][toY][toX] = pieceSweep;
5438     DrawPosition(FALSE, boards[currentMove]);
5439     boards[currentMove][toY][toX] = piece;
5440 }
5441 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5442 void
5443 AlphaRank (char *move, int n)
5444 {
5445 //    char *p = move, c; int x, y;
5446
5447     if (appData.debugMode) {
5448         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5449     }
5450
5451     if(move[1]=='*' &&
5452        move[2]>='0' && move[2]<='9' &&
5453        move[3]>='a' && move[3]<='x'    ) {
5454         move[1] = '@';
5455         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5456         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5457     } else
5458     if(move[0]>='0' && move[0]<='9' &&
5459        move[1]>='a' && move[1]<='x' &&
5460        move[2]>='0' && move[2]<='9' &&
5461        move[3]>='a' && move[3]<='x'    ) {
5462         /* input move, Shogi -> normal */
5463         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5464         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5465         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5466         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5467     } else
5468     if(move[1]=='@' &&
5469        move[3]>='0' && move[3]<='9' &&
5470        move[2]>='a' && move[2]<='x'    ) {
5471         move[1] = '*';
5472         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5473         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5474     } else
5475     if(
5476        move[0]>='a' && move[0]<='x' &&
5477        move[3]>='0' && move[3]<='9' &&
5478        move[2]>='a' && move[2]<='x'    ) {
5479          /* output move, normal -> Shogi */
5480         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5481         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5482         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5483         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5484         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5485     }
5486     if (appData.debugMode) {
5487         fprintf(debugFP, "   out = '%s'\n", move);
5488     }
5489 }
5490
5491 char yy_textstr[8000];
5492
5493 /* Parser for moves from gnuchess, ICS, or user typein box */
5494 Boolean
5495 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5496 {
5497     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5498
5499     switch (*moveType) {
5500       case WhitePromotion:
5501       case BlackPromotion:
5502       case WhiteNonPromotion:
5503       case BlackNonPromotion:
5504       case NormalMove:
5505       case FirstLeg:
5506       case WhiteCapturesEnPassant:
5507       case BlackCapturesEnPassant:
5508       case WhiteKingSideCastle:
5509       case WhiteQueenSideCastle:
5510       case BlackKingSideCastle:
5511       case BlackQueenSideCastle:
5512       case WhiteKingSideCastleWild:
5513       case WhiteQueenSideCastleWild:
5514       case BlackKingSideCastleWild:
5515       case BlackQueenSideCastleWild:
5516       /* Code added by Tord: */
5517       case WhiteHSideCastleFR:
5518       case WhiteASideCastleFR:
5519       case BlackHSideCastleFR:
5520       case BlackASideCastleFR:
5521       /* End of code added by Tord */
5522       case IllegalMove:         /* bug or odd chess variant */
5523         *fromX = currentMoveString[0] - AAA;
5524         *fromY = currentMoveString[1] - ONE;
5525         *toX = currentMoveString[2] - AAA;
5526         *toY = currentMoveString[3] - ONE;
5527         *promoChar = currentMoveString[4];
5528         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5529             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5530     if (appData.debugMode) {
5531         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5532     }
5533             *fromX = *fromY = *toX = *toY = 0;
5534             return FALSE;
5535         }
5536         if (appData.testLegality) {
5537           return (*moveType != IllegalMove);
5538         } else {
5539           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5540                          // [HGM] lion: if this is a double move we are less critical
5541                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5542         }
5543
5544       case WhiteDrop:
5545       case BlackDrop:
5546         *fromX = *moveType == WhiteDrop ?
5547           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5548           (int) CharToPiece(ToLower(currentMoveString[0]));
5549         *fromY = DROP_RANK;
5550         *toX = currentMoveString[2] - AAA;
5551         *toY = currentMoveString[3] - ONE;
5552         *promoChar = NULLCHAR;
5553         return TRUE;
5554
5555       case AmbiguousMove:
5556       case ImpossibleMove:
5557       case EndOfFile:
5558       case ElapsedTime:
5559       case Comment:
5560       case PGNTag:
5561       case NAG:
5562       case WhiteWins:
5563       case BlackWins:
5564       case GameIsDrawn:
5565       default:
5566     if (appData.debugMode) {
5567         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5568     }
5569         /* bug? */
5570         *fromX = *fromY = *toX = *toY = 0;
5571         *promoChar = NULLCHAR;
5572         return FALSE;
5573     }
5574 }
5575
5576 Boolean pushed = FALSE;
5577 char *lastParseAttempt;
5578
5579 void
5580 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5581 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5582   int fromX, fromY, toX, toY; char promoChar;
5583   ChessMove moveType;
5584   Boolean valid;
5585   int nr = 0;
5586
5587   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5588   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5589     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5590     pushed = TRUE;
5591   }
5592   endPV = forwardMostMove;
5593   do {
5594     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5595     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5596     lastParseAttempt = pv;
5597     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5598     if(!valid && nr == 0 &&
5599        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5600         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5601         // Hande case where played move is different from leading PV move
5602         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5603         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5604         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5605         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5606           endPV += 2; // if position different, keep this
5607           moveList[endPV-1][0] = fromX + AAA;
5608           moveList[endPV-1][1] = fromY + ONE;
5609           moveList[endPV-1][2] = toX + AAA;
5610           moveList[endPV-1][3] = toY + ONE;
5611           parseList[endPV-1][0] = NULLCHAR;
5612           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5613         }
5614       }
5615     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5616     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5617     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5618     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5619         valid++; // allow comments in PV
5620         continue;
5621     }
5622     nr++;
5623     if(endPV+1 > framePtr) break; // no space, truncate
5624     if(!valid) break;
5625     endPV++;
5626     CopyBoard(boards[endPV], boards[endPV-1]);
5627     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5628     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5629     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5630     CoordsToAlgebraic(boards[endPV - 1],
5631                              PosFlags(endPV - 1),
5632                              fromY, fromX, toY, toX, promoChar,
5633                              parseList[endPV - 1]);
5634   } while(valid);
5635   if(atEnd == 2) return; // used hidden, for PV conversion
5636   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5637   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5638   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5639                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5640   DrawPosition(TRUE, boards[currentMove]);
5641 }
5642
5643 int
5644 MultiPV (ChessProgramState *cps)
5645 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5646         int i;
5647         for(i=0; i<cps->nrOptions; i++)
5648             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5649                 return i;
5650         return -1;
5651 }
5652
5653 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5654
5655 Boolean
5656 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5657 {
5658         int startPV, multi, lineStart, origIndex = index;
5659         char *p, buf2[MSG_SIZ];
5660         ChessProgramState *cps = (pane ? &second : &first);
5661
5662         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5663         lastX = x; lastY = y;
5664         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5665         lineStart = startPV = index;
5666         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5667         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5668         index = startPV;
5669         do{ while(buf[index] && buf[index] != '\n') index++;
5670         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5671         buf[index] = 0;
5672         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5673                 int n = cps->option[multi].value;
5674                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5675                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5676                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5677                 cps->option[multi].value = n;
5678                 *start = *end = 0;
5679                 return FALSE;
5680         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5681                 ExcludeClick(origIndex - lineStart);
5682                 return FALSE;
5683         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5684                 Collapse(origIndex - lineStart);
5685                 return FALSE;
5686         }
5687         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5688         *start = startPV; *end = index-1;
5689         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5690         return TRUE;
5691 }
5692
5693 char *
5694 PvToSAN (char *pv)
5695 {
5696         static char buf[10*MSG_SIZ];
5697         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5698         *buf = NULLCHAR;
5699         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5700         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5701         for(i = forwardMostMove; i<endPV; i++){
5702             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5703             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5704             k += strlen(buf+k);
5705         }
5706         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5707         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5708         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5709         endPV = savedEnd;
5710         return buf;
5711 }
5712
5713 Boolean
5714 LoadPV (int x, int y)
5715 { // called on right mouse click to load PV
5716   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5717   lastX = x; lastY = y;
5718   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5719   extendGame = FALSE;
5720   return TRUE;
5721 }
5722
5723 void
5724 UnLoadPV ()
5725 {
5726   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5727   if(endPV < 0) return;
5728   if(appData.autoCopyPV) CopyFENToClipboard();
5729   endPV = -1;
5730   if(extendGame && currentMove > forwardMostMove) {
5731         Boolean saveAnimate = appData.animate;
5732         if(pushed) {
5733             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5734                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5735             } else storedGames--; // abandon shelved tail of original game
5736         }
5737         pushed = FALSE;
5738         forwardMostMove = currentMove;
5739         currentMove = oldFMM;
5740         appData.animate = FALSE;
5741         ToNrEvent(forwardMostMove);
5742         appData.animate = saveAnimate;
5743   }
5744   currentMove = forwardMostMove;
5745   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5746   ClearPremoveHighlights();
5747   DrawPosition(TRUE, boards[currentMove]);
5748 }
5749
5750 void
5751 MovePV (int x, int y, int h)
5752 { // step through PV based on mouse coordinates (called on mouse move)
5753   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5754
5755   // we must somehow check if right button is still down (might be released off board!)
5756   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5757   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5758   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5759   if(!step) return;
5760   lastX = x; lastY = y;
5761
5762   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5763   if(endPV < 0) return;
5764   if(y < margin) step = 1; else
5765   if(y > h - margin) step = -1;
5766   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5767   currentMove += step;
5768   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5769   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5770                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5771   DrawPosition(FALSE, boards[currentMove]);
5772 }
5773
5774
5775 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5776 // All positions will have equal probability, but the current method will not provide a unique
5777 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5778 #define DARK 1
5779 #define LITE 2
5780 #define ANY 3
5781
5782 int squaresLeft[4];
5783 int piecesLeft[(int)BlackPawn];
5784 int seed, nrOfShuffles;
5785
5786 void
5787 GetPositionNumber ()
5788 {       // sets global variable seed
5789         int i;
5790
5791         seed = appData.defaultFrcPosition;
5792         if(seed < 0) { // randomize based on time for negative FRC position numbers
5793                 for(i=0; i<50; i++) seed += random();
5794                 seed = random() ^ random() >> 8 ^ random() << 8;
5795                 if(seed<0) seed = -seed;
5796         }
5797 }
5798
5799 int
5800 put (Board board, int pieceType, int rank, int n, int shade)
5801 // put the piece on the (n-1)-th empty squares of the given shade
5802 {
5803         int i;
5804
5805         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5806                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5807                         board[rank][i] = (ChessSquare) pieceType;
5808                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5809                         squaresLeft[ANY]--;
5810                         piecesLeft[pieceType]--;
5811                         return i;
5812                 }
5813         }
5814         return -1;
5815 }
5816
5817
5818 void
5819 AddOnePiece (Board board, int pieceType, int rank, int shade)
5820 // calculate where the next piece goes, (any empty square), and put it there
5821 {
5822         int i;
5823
5824         i = seed % squaresLeft[shade];
5825         nrOfShuffles *= squaresLeft[shade];
5826         seed /= squaresLeft[shade];
5827         put(board, pieceType, rank, i, shade);
5828 }
5829
5830 void
5831 AddTwoPieces (Board board, int pieceType, int rank)
5832 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5833 {
5834         int i, n=squaresLeft[ANY], j=n-1, k;
5835
5836         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5837         i = seed % k;  // pick one
5838         nrOfShuffles *= k;
5839         seed /= k;
5840         while(i >= j) i -= j--;
5841         j = n - 1 - j; i += j;
5842         put(board, pieceType, rank, j, ANY);
5843         put(board, pieceType, rank, i, ANY);
5844 }
5845
5846 void
5847 SetUpShuffle (Board board, int number)
5848 {
5849         int i, p, first=1;
5850
5851         GetPositionNumber(); nrOfShuffles = 1;
5852
5853         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5854         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5855         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5856
5857         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5858
5859         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5860             p = (int) board[0][i];
5861             if(p < (int) BlackPawn) piecesLeft[p] ++;
5862             board[0][i] = EmptySquare;
5863         }
5864
5865         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5866             // shuffles restricted to allow normal castling put KRR first
5867             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5868                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5869             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5870                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5871             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5872                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5873             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5874                 put(board, WhiteRook, 0, 0, ANY);
5875             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5876         }
5877
5878         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5879             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5880             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5881                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5882                 while(piecesLeft[p] >= 2) {
5883                     AddOnePiece(board, p, 0, LITE);
5884                     AddOnePiece(board, p, 0, DARK);
5885                 }
5886                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5887             }
5888
5889         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5890             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5891             // but we leave King and Rooks for last, to possibly obey FRC restriction
5892             if(p == (int)WhiteRook) continue;
5893             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5894             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5895         }
5896
5897         // now everything is placed, except perhaps King (Unicorn) and Rooks
5898
5899         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5900             // Last King gets castling rights
5901             while(piecesLeft[(int)WhiteUnicorn]) {
5902                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5903                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5904             }
5905
5906             while(piecesLeft[(int)WhiteKing]) {
5907                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5908                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5909             }
5910
5911
5912         } else {
5913             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5914             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5915         }
5916
5917         // Only Rooks can be left; simply place them all
5918         while(piecesLeft[(int)WhiteRook]) {
5919                 i = put(board, WhiteRook, 0, 0, ANY);
5920                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5921                         if(first) {
5922                                 first=0;
5923                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5924                         }
5925                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5926                 }
5927         }
5928         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5929             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5930         }
5931
5932         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5933 }
5934
5935 int
5936 SetCharTable (char *table, const char * map)
5937 /* [HGM] moved here from winboard.c because of its general usefulness */
5938 /*       Basically a safe strcpy that uses the last character as King */
5939 {
5940     int result = FALSE; int NrPieces;
5941
5942     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5943                     && NrPieces >= 12 && !(NrPieces&1)) {
5944         int i; /* [HGM] Accept even length from 12 to 34 */
5945
5946         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5947         for( i=0; i<NrPieces/2-1; i++ ) {
5948             table[i] = map[i];
5949             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5950         }
5951         table[(int) WhiteKing]  = map[NrPieces/2-1];
5952         table[(int) BlackKing]  = map[NrPieces-1];
5953
5954         result = TRUE;
5955     }
5956
5957     return result;
5958 }
5959
5960 void
5961 Prelude (Board board)
5962 {       // [HGM] superchess: random selection of exo-pieces
5963         int i, j, k; ChessSquare p;
5964         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5965
5966         GetPositionNumber(); // use FRC position number
5967
5968         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5969             SetCharTable(pieceToChar, appData.pieceToCharTable);
5970             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5971                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5972         }
5973
5974         j = seed%4;                 seed /= 4;
5975         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5976         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5977         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5978         j = seed%3 + (seed%3 >= j); seed /= 3;
5979         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5980         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5981         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5982         j = seed%3;                 seed /= 3;
5983         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5984         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5985         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5986         j = seed%2 + (seed%2 >= j); seed /= 2;
5987         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5988         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5989         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5990         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5991         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5992         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5993         put(board, exoPieces[0],    0, 0, ANY);
5994         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5995 }
5996
5997 void
5998 InitPosition (int redraw)
5999 {
6000     ChessSquare (* pieces)[BOARD_FILES];
6001     int i, j, pawnRow=1, pieceRows=1, overrule,
6002     oldx = gameInfo.boardWidth,
6003     oldy = gameInfo.boardHeight,
6004     oldh = gameInfo.holdingsWidth;
6005     static int oldv;
6006
6007     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6008
6009     /* [AS] Initialize pv info list [HGM] and game status */
6010     {
6011         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6012             pvInfoList[i].depth = 0;
6013             boards[i][EP_STATUS] = EP_NONE;
6014             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6015         }
6016
6017         initialRulePlies = 0; /* 50-move counter start */
6018
6019         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6020         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6021     }
6022
6023
6024     /* [HGM] logic here is completely changed. In stead of full positions */
6025     /* the initialized data only consist of the two backranks. The switch */
6026     /* selects which one we will use, which is than copied to the Board   */
6027     /* initialPosition, which for the rest is initialized by Pawns and    */
6028     /* empty squares. This initial position is then copied to boards[0],  */
6029     /* possibly after shuffling, so that it remains available.            */
6030
6031     gameInfo.holdingsWidth = 0; /* default board sizes */
6032     gameInfo.boardWidth    = 8;
6033     gameInfo.boardHeight   = 8;
6034     gameInfo.holdingsSize  = 0;
6035     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6036     for(i=0; i<BOARD_FILES-6; i++)
6037       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6038     initialPosition[EP_STATUS] = EP_NONE;
6039     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6040     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6041     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6042          SetCharTable(pieceNickName, appData.pieceNickNames);
6043     else SetCharTable(pieceNickName, "............");
6044     pieces = FIDEArray;
6045
6046     switch (gameInfo.variant) {
6047     case VariantFischeRandom:
6048       shuffleOpenings = TRUE;
6049       appData.fischerCastling = TRUE;
6050     default:
6051       break;
6052     case VariantShatranj:
6053       pieces = ShatranjArray;
6054       nrCastlingRights = 0;
6055       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6056       break;
6057     case VariantMakruk:
6058       pieces = makrukArray;
6059       nrCastlingRights = 0;
6060       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6061       break;
6062     case VariantASEAN:
6063       pieces = aseanArray;
6064       nrCastlingRights = 0;
6065       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6066       break;
6067     case VariantTwoKings:
6068       pieces = twoKingsArray;
6069       break;
6070     case VariantGrand:
6071       pieces = GrandArray;
6072       nrCastlingRights = 0;
6073       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6074       gameInfo.boardWidth = 10;
6075       gameInfo.boardHeight = 10;
6076       gameInfo.holdingsSize = 7;
6077       break;
6078     case VariantCapaRandom:
6079       shuffleOpenings = TRUE;
6080       appData.fischerCastling = TRUE;
6081     case VariantCapablanca:
6082       pieces = CapablancaArray;
6083       gameInfo.boardWidth = 10;
6084       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6085       break;
6086     case VariantGothic:
6087       pieces = GothicArray;
6088       gameInfo.boardWidth = 10;
6089       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6090       break;
6091     case VariantSChess:
6092       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6093       gameInfo.holdingsSize = 7;
6094       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6095       break;
6096     case VariantJanus:
6097       pieces = JanusArray;
6098       gameInfo.boardWidth = 10;
6099       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6100       nrCastlingRights = 6;
6101         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6102         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6103         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6104         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6105         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6106         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6107       break;
6108     case VariantFalcon:
6109       pieces = FalconArray;
6110       gameInfo.boardWidth = 10;
6111       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6112       break;
6113     case VariantXiangqi:
6114       pieces = XiangqiArray;
6115       gameInfo.boardWidth  = 9;
6116       gameInfo.boardHeight = 10;
6117       nrCastlingRights = 0;
6118       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6119       break;
6120     case VariantShogi:
6121       pieces = ShogiArray;
6122       gameInfo.boardWidth  = 9;
6123       gameInfo.boardHeight = 9;
6124       gameInfo.holdingsSize = 7;
6125       nrCastlingRights = 0;
6126       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6127       break;
6128     case VariantChu:
6129       pieces = ChuArray; pieceRows = 3;
6130       gameInfo.boardWidth  = 12;
6131       gameInfo.boardHeight = 12;
6132       nrCastlingRights = 0;
6133       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6134                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6135       break;
6136     case VariantCourier:
6137       pieces = CourierArray;
6138       gameInfo.boardWidth  = 12;
6139       nrCastlingRights = 0;
6140       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6141       break;
6142     case VariantKnightmate:
6143       pieces = KnightmateArray;
6144       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6145       break;
6146     case VariantSpartan:
6147       pieces = SpartanArray;
6148       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6149       break;
6150     case VariantLion:
6151       pieces = lionArray;
6152       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6153       break;
6154     case VariantChuChess:
6155       pieces = ChuChessArray;
6156       gameInfo.boardWidth = 10;
6157       gameInfo.boardHeight = 10;
6158       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6159       break;
6160     case VariantFairy:
6161       pieces = fairyArray;
6162       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6163       break;
6164     case VariantGreat:
6165       pieces = GreatArray;
6166       gameInfo.boardWidth = 10;
6167       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6168       gameInfo.holdingsSize = 8;
6169       break;
6170     case VariantSuper:
6171       pieces = FIDEArray;
6172       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6173       gameInfo.holdingsSize = 8;
6174       startedFromSetupPosition = TRUE;
6175       break;
6176     case VariantCrazyhouse:
6177     case VariantBughouse:
6178       pieces = FIDEArray;
6179       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6180       gameInfo.holdingsSize = 5;
6181       break;
6182     case VariantWildCastle:
6183       pieces = FIDEArray;
6184       /* !!?shuffle with kings guaranteed to be on d or e file */
6185       shuffleOpenings = 1;
6186       break;
6187     case VariantNoCastle:
6188       pieces = FIDEArray;
6189       nrCastlingRights = 0;
6190       /* !!?unconstrained back-rank shuffle */
6191       shuffleOpenings = 1;
6192       break;
6193     }
6194
6195     overrule = 0;
6196     if(appData.NrFiles >= 0) {
6197         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6198         gameInfo.boardWidth = appData.NrFiles;
6199     }
6200     if(appData.NrRanks >= 0) {
6201         gameInfo.boardHeight = appData.NrRanks;
6202     }
6203     if(appData.holdingsSize >= 0) {
6204         i = appData.holdingsSize;
6205         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6206         gameInfo.holdingsSize = i;
6207     }
6208     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6209     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6210         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6211
6212     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6213     if(pawnRow < 1) pawnRow = 1;
6214     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6215        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6216     if(gameInfo.variant == VariantChu) pawnRow = 3;
6217
6218     /* User pieceToChar list overrules defaults */
6219     if(appData.pieceToCharTable != NULL)
6220         SetCharTable(pieceToChar, appData.pieceToCharTable);
6221
6222     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6223
6224         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6225             s = (ChessSquare) 0; /* account holding counts in guard band */
6226         for( i=0; i<BOARD_HEIGHT; i++ )
6227             initialPosition[i][j] = s;
6228
6229         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6230         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6231         initialPosition[pawnRow][j] = WhitePawn;
6232         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6233         if(gameInfo.variant == VariantXiangqi) {
6234             if(j&1) {
6235                 initialPosition[pawnRow][j] =
6236                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6237                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6238                    initialPosition[2][j] = WhiteCannon;
6239                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6240                 }
6241             }
6242         }
6243         if(gameInfo.variant == VariantChu) {
6244              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6245                initialPosition[pawnRow+1][j] = WhiteCobra,
6246                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6247              for(i=1; i<pieceRows; i++) {
6248                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6249                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6250              }
6251         }
6252         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6253             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6254                initialPosition[0][j] = WhiteRook;
6255                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6256             }
6257         }
6258         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6259     }
6260     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6261     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6262
6263             j=BOARD_LEFT+1;
6264             initialPosition[1][j] = WhiteBishop;
6265             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6266             j=BOARD_RGHT-2;
6267             initialPosition[1][j] = WhiteRook;
6268             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6269     }
6270
6271     if( nrCastlingRights == -1) {
6272         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6273         /*       This sets default castling rights from none to normal corners   */
6274         /* Variants with other castling rights must set them themselves above    */
6275         nrCastlingRights = 6;
6276
6277         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6278         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6279         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6280         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6281         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6282         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6283      }
6284
6285      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6286      if(gameInfo.variant == VariantGreat) { // promotion commoners
6287         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6288         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6289         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6290         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6291      }
6292      if( gameInfo.variant == VariantSChess ) {
6293       initialPosition[1][0] = BlackMarshall;
6294       initialPosition[2][0] = BlackAngel;
6295       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6296       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6297       initialPosition[1][1] = initialPosition[2][1] =
6298       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6299      }
6300   if (appData.debugMode) {
6301     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6302   }
6303     if(shuffleOpenings) {
6304         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6305         startedFromSetupPosition = TRUE;
6306     }
6307     if(startedFromPositionFile) {
6308       /* [HGM] loadPos: use PositionFile for every new game */
6309       CopyBoard(initialPosition, filePosition);
6310       for(i=0; i<nrCastlingRights; i++)
6311           initialRights[i] = filePosition[CASTLING][i];
6312       startedFromSetupPosition = TRUE;
6313     }
6314
6315     CopyBoard(boards[0], initialPosition);
6316
6317     if(oldx != gameInfo.boardWidth ||
6318        oldy != gameInfo.boardHeight ||
6319        oldv != gameInfo.variant ||
6320        oldh != gameInfo.holdingsWidth
6321                                          )
6322             InitDrawingSizes(-2 ,0);
6323
6324     oldv = gameInfo.variant;
6325     if (redraw)
6326       DrawPosition(TRUE, boards[currentMove]);
6327 }
6328
6329 void
6330 SendBoard (ChessProgramState *cps, int moveNum)
6331 {
6332     char message[MSG_SIZ];
6333
6334     if (cps->useSetboard) {
6335       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6336       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6337       SendToProgram(message, cps);
6338       free(fen);
6339
6340     } else {
6341       ChessSquare *bp;
6342       int i, j, left=0, right=BOARD_WIDTH;
6343       /* Kludge to set black to move, avoiding the troublesome and now
6344        * deprecated "black" command.
6345        */
6346       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6347         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6348
6349       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6350
6351       SendToProgram("edit\n", cps);
6352       SendToProgram("#\n", cps);
6353       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6354         bp = &boards[moveNum][i][left];
6355         for (j = left; j < right; j++, bp++) {
6356           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6357           if ((int) *bp < (int) BlackPawn) {
6358             if(j == BOARD_RGHT+1)
6359                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6360             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6361             if(message[0] == '+' || message[0] == '~') {
6362               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6363                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6364                         AAA + j, ONE + i);
6365             }
6366             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6367                 message[1] = BOARD_RGHT   - 1 - j + '1';
6368                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6369             }
6370             SendToProgram(message, cps);
6371           }
6372         }
6373       }
6374
6375       SendToProgram("c\n", cps);
6376       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6377         bp = &boards[moveNum][i][left];
6378         for (j = left; j < right; j++, bp++) {
6379           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6380           if (((int) *bp != (int) EmptySquare)
6381               && ((int) *bp >= (int) BlackPawn)) {
6382             if(j == BOARD_LEFT-2)
6383                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6384             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6385                     AAA + j, ONE + i);
6386             if(message[0] == '+' || message[0] == '~') {
6387               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6388                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6389                         AAA + j, ONE + i);
6390             }
6391             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6392                 message[1] = BOARD_RGHT   - 1 - j + '1';
6393                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6394             }
6395             SendToProgram(message, cps);
6396           }
6397         }
6398       }
6399
6400       SendToProgram(".\n", cps);
6401     }
6402     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6403 }
6404
6405 char exclusionHeader[MSG_SIZ];
6406 int exCnt, excludePtr;
6407 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6408 static Exclusion excluTab[200];
6409 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6410
6411 static void
6412 WriteMap (int s)
6413 {
6414     int j;
6415     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6416     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6417 }
6418
6419 static void
6420 ClearMap ()
6421 {
6422     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6423     excludePtr = 24; exCnt = 0;
6424     WriteMap(0);
6425 }
6426
6427 static void
6428 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6429 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6430     char buf[2*MOVE_LEN], *p;
6431     Exclusion *e = excluTab;
6432     int i;
6433     for(i=0; i<exCnt; i++)
6434         if(e[i].ff == fromX && e[i].fr == fromY &&
6435            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6436     if(i == exCnt) { // was not in exclude list; add it
6437         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6438         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6439             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6440             return; // abort
6441         }
6442         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6443         excludePtr++; e[i].mark = excludePtr++;
6444         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6445         exCnt++;
6446     }
6447     exclusionHeader[e[i].mark] = state;
6448 }
6449
6450 static int
6451 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6452 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6453     char buf[MSG_SIZ];
6454     int j, k;
6455     ChessMove moveType;
6456     if((signed char)promoChar == -1) { // kludge to indicate best move
6457         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6458             return 1; // if unparsable, abort
6459     }
6460     // update exclusion map (resolving toggle by consulting existing state)
6461     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6462     j = k%8; k >>= 3;
6463     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6464     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6465          excludeMap[k] |=   1<<j;
6466     else excludeMap[k] &= ~(1<<j);
6467     // update header
6468     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6469     // inform engine
6470     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6471     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6472     SendToBoth(buf);
6473     return (state == '+');
6474 }
6475
6476 static void
6477 ExcludeClick (int index)
6478 {
6479     int i, j;
6480     Exclusion *e = excluTab;
6481     if(index < 25) { // none, best or tail clicked
6482         if(index < 13) { // none: include all
6483             WriteMap(0); // clear map
6484             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6485             SendToBoth("include all\n"); // and inform engine
6486         } else if(index > 18) { // tail
6487             if(exclusionHeader[19] == '-') { // tail was excluded
6488                 SendToBoth("include all\n");
6489                 WriteMap(0); // clear map completely
6490                 // now re-exclude selected moves
6491                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6492                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6493             } else { // tail was included or in mixed state
6494                 SendToBoth("exclude all\n");
6495                 WriteMap(0xFF); // fill map completely
6496                 // now re-include selected moves
6497                 j = 0; // count them
6498                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6499                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6500                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6501             }
6502         } else { // best
6503             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6504         }
6505     } else {
6506         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6507             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6508             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6509             break;
6510         }
6511     }
6512 }
6513
6514 ChessSquare
6515 DefaultPromoChoice (int white)
6516 {
6517     ChessSquare result;
6518     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6519        gameInfo.variant == VariantMakruk)
6520         result = WhiteFerz; // no choice
6521     else if(gameInfo.variant == VariantASEAN)
6522         result = WhiteRook; // no choice
6523     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6524         result= WhiteKing; // in Suicide Q is the last thing we want
6525     else if(gameInfo.variant == VariantSpartan)
6526         result = white ? WhiteQueen : WhiteAngel;
6527     else result = WhiteQueen;
6528     if(!white) result = WHITE_TO_BLACK result;
6529     return result;
6530 }
6531
6532 static int autoQueen; // [HGM] oneclick
6533
6534 int
6535 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6536 {
6537     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6538     /* [HGM] add Shogi promotions */
6539     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6540     ChessSquare piece, partner;
6541     ChessMove moveType;
6542     Boolean premove;
6543
6544     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6545     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6546
6547     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6548       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6549         return FALSE;
6550
6551     piece = boards[currentMove][fromY][fromX];
6552     if(gameInfo.variant == VariantChu) {
6553         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6554         promotionZoneSize = BOARD_HEIGHT/3;
6555         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6556     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6557         promotionZoneSize = BOARD_HEIGHT/3;
6558         highestPromotingPiece = (int)WhiteAlfil;
6559     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6560         promotionZoneSize = 3;
6561     }
6562
6563     // Treat Lance as Pawn when it is not representing Amazon or Lance
6564     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6565         if(piece == WhiteLance) piece = WhitePawn; else
6566         if(piece == BlackLance) piece = BlackPawn;
6567     }
6568
6569     // next weed out all moves that do not touch the promotion zone at all
6570     if((int)piece >= BlackPawn) {
6571         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6572              return FALSE;
6573         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6574         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6575     } else {
6576         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6577            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6578         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6579              return FALSE;
6580     }
6581
6582     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6583
6584     // weed out mandatory Shogi promotions
6585     if(gameInfo.variant == VariantShogi) {
6586         if(piece >= BlackPawn) {
6587             if(toY == 0 && piece == BlackPawn ||
6588                toY == 0 && piece == BlackQueen ||
6589                toY <= 1 && piece == BlackKnight) {
6590                 *promoChoice = '+';
6591                 return FALSE;
6592             }
6593         } else {
6594             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6595                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6596                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6597                 *promoChoice = '+';
6598                 return FALSE;
6599             }
6600         }
6601     }
6602
6603     // weed out obviously illegal Pawn moves
6604     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6605         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6606         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6607         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6608         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6609         // note we are not allowed to test for valid (non-)capture, due to premove
6610     }
6611
6612     // we either have a choice what to promote to, or (in Shogi) whether to promote
6613     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6614        gameInfo.variant == VariantMakruk) {
6615         ChessSquare p=BlackFerz;  // no choice
6616         while(p < EmptySquare) {  //but make sure we use piece that exists
6617             *promoChoice = PieceToChar(p++);
6618             if(*promoChoice != '.') break;
6619         }
6620         return FALSE;
6621     }
6622     // no sense asking what we must promote to if it is going to explode...
6623     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6624         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6625         return FALSE;
6626     }
6627     // give caller the default choice even if we will not make it
6628     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6629     partner = piece; // pieces can promote if the pieceToCharTable says so
6630     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6631     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6632     if(        sweepSelect && gameInfo.variant != VariantGreat
6633                            && gameInfo.variant != VariantGrand
6634                            && gameInfo.variant != VariantSuper) return FALSE;
6635     if(autoQueen) return FALSE; // predetermined
6636
6637     // suppress promotion popup on illegal moves that are not premoves
6638     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6639               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6640     if(appData.testLegality && !premove) {
6641         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6642                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6643         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6644         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6645             return FALSE;
6646     }
6647
6648     return TRUE;
6649 }
6650
6651 int
6652 InPalace (int row, int column)
6653 {   /* [HGM] for Xiangqi */
6654     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6655          column < (BOARD_WIDTH + 4)/2 &&
6656          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6657     return FALSE;
6658 }
6659
6660 int
6661 PieceForSquare (int x, int y)
6662 {
6663   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6664      return -1;
6665   else
6666      return boards[currentMove][y][x];
6667 }
6668
6669 int
6670 OKToStartUserMove (int x, int y)
6671 {
6672     ChessSquare from_piece;
6673     int white_piece;
6674
6675     if (matchMode) return FALSE;
6676     if (gameMode == EditPosition) return TRUE;
6677
6678     if (x >= 0 && y >= 0)
6679       from_piece = boards[currentMove][y][x];
6680     else
6681       from_piece = EmptySquare;
6682
6683     if (from_piece == EmptySquare) return FALSE;
6684
6685     white_piece = (int)from_piece >= (int)WhitePawn &&
6686       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6687
6688     switch (gameMode) {
6689       case AnalyzeFile:
6690       case TwoMachinesPlay:
6691       case EndOfGame:
6692         return FALSE;
6693
6694       case IcsObserving:
6695       case IcsIdle:
6696         return FALSE;
6697
6698       case MachinePlaysWhite:
6699       case IcsPlayingBlack:
6700         if (appData.zippyPlay) return FALSE;
6701         if (white_piece) {
6702             DisplayMoveError(_("You are playing Black"));
6703             return FALSE;
6704         }
6705         break;
6706
6707       case MachinePlaysBlack:
6708       case IcsPlayingWhite:
6709         if (appData.zippyPlay) return FALSE;
6710         if (!white_piece) {
6711             DisplayMoveError(_("You are playing White"));
6712             return FALSE;
6713         }
6714         break;
6715
6716       case PlayFromGameFile:
6717             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6718       case EditGame:
6719         if (!white_piece && WhiteOnMove(currentMove)) {
6720             DisplayMoveError(_("It is White's turn"));
6721             return FALSE;
6722         }
6723         if (white_piece && !WhiteOnMove(currentMove)) {
6724             DisplayMoveError(_("It is Black's turn"));
6725             return FALSE;
6726         }
6727         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6728             /* Editing correspondence game history */
6729             /* Could disallow this or prompt for confirmation */
6730             cmailOldMove = -1;
6731         }
6732         break;
6733
6734       case BeginningOfGame:
6735         if (appData.icsActive) return FALSE;
6736         if (!appData.noChessProgram) {
6737             if (!white_piece) {
6738                 DisplayMoveError(_("You are playing White"));
6739                 return FALSE;
6740             }
6741         }
6742         break;
6743
6744       case Training:
6745         if (!white_piece && WhiteOnMove(currentMove)) {
6746             DisplayMoveError(_("It is White's turn"));
6747             return FALSE;
6748         }
6749         if (white_piece && !WhiteOnMove(currentMove)) {
6750             DisplayMoveError(_("It is Black's turn"));
6751             return FALSE;
6752         }
6753         break;
6754
6755       default:
6756       case IcsExamining:
6757         break;
6758     }
6759     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6760         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6761         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6762         && gameMode != AnalyzeFile && gameMode != Training) {
6763         DisplayMoveError(_("Displayed position is not current"));
6764         return FALSE;
6765     }
6766     return TRUE;
6767 }
6768
6769 Boolean
6770 OnlyMove (int *x, int *y, Boolean captures)
6771 {
6772     DisambiguateClosure cl;
6773     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6774     switch(gameMode) {
6775       case MachinePlaysBlack:
6776       case IcsPlayingWhite:
6777       case BeginningOfGame:
6778         if(!WhiteOnMove(currentMove)) return FALSE;
6779         break;
6780       case MachinePlaysWhite:
6781       case IcsPlayingBlack:
6782         if(WhiteOnMove(currentMove)) return FALSE;
6783         break;
6784       case EditGame:
6785         break;
6786       default:
6787         return FALSE;
6788     }
6789     cl.pieceIn = EmptySquare;
6790     cl.rfIn = *y;
6791     cl.ffIn = *x;
6792     cl.rtIn = -1;
6793     cl.ftIn = -1;
6794     cl.promoCharIn = NULLCHAR;
6795     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6796     if( cl.kind == NormalMove ||
6797         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6798         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6799         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6800       fromX = cl.ff;
6801       fromY = cl.rf;
6802       *x = cl.ft;
6803       *y = cl.rt;
6804       return TRUE;
6805     }
6806     if(cl.kind != ImpossibleMove) return FALSE;
6807     cl.pieceIn = EmptySquare;
6808     cl.rfIn = -1;
6809     cl.ffIn = -1;
6810     cl.rtIn = *y;
6811     cl.ftIn = *x;
6812     cl.promoCharIn = NULLCHAR;
6813     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6814     if( cl.kind == NormalMove ||
6815         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6816         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6817         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6818       fromX = cl.ff;
6819       fromY = cl.rf;
6820       *x = cl.ft;
6821       *y = cl.rt;
6822       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6823       return TRUE;
6824     }
6825     return FALSE;
6826 }
6827
6828 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6829 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6830 int lastLoadGameUseList = FALSE;
6831 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6832 ChessMove lastLoadGameStart = EndOfFile;
6833 int doubleClick;
6834 Boolean addToBookFlag;
6835
6836 void
6837 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6838 {
6839     ChessMove moveType;
6840     ChessSquare pup;
6841     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6842
6843     /* Check if the user is playing in turn.  This is complicated because we
6844        let the user "pick up" a piece before it is his turn.  So the piece he
6845        tried to pick up may have been captured by the time he puts it down!
6846        Therefore we use the color the user is supposed to be playing in this
6847        test, not the color of the piece that is currently on the starting
6848        square---except in EditGame mode, where the user is playing both
6849        sides; fortunately there the capture race can't happen.  (It can
6850        now happen in IcsExamining mode, but that's just too bad.  The user
6851        will get a somewhat confusing message in that case.)
6852        */
6853
6854     switch (gameMode) {
6855       case AnalyzeFile:
6856       case TwoMachinesPlay:
6857       case EndOfGame:
6858       case IcsObserving:
6859       case IcsIdle:
6860         /* We switched into a game mode where moves are not accepted,
6861            perhaps while the mouse button was down. */
6862         return;
6863
6864       case MachinePlaysWhite:
6865         /* User is moving for Black */
6866         if (WhiteOnMove(currentMove)) {
6867             DisplayMoveError(_("It is White's turn"));
6868             return;
6869         }
6870         break;
6871
6872       case MachinePlaysBlack:
6873         /* User is moving for White */
6874         if (!WhiteOnMove(currentMove)) {
6875             DisplayMoveError(_("It is Black's turn"));
6876             return;
6877         }
6878         break;
6879
6880       case PlayFromGameFile:
6881             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6882       case EditGame:
6883       case IcsExamining:
6884       case BeginningOfGame:
6885       case AnalyzeMode:
6886       case Training:
6887         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6888         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6889             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6890             /* User is moving for Black */
6891             if (WhiteOnMove(currentMove)) {
6892                 DisplayMoveError(_("It is White's turn"));
6893                 return;
6894             }
6895         } else {
6896             /* User is moving for White */
6897             if (!WhiteOnMove(currentMove)) {
6898                 DisplayMoveError(_("It is Black's turn"));
6899                 return;
6900             }
6901         }
6902         break;
6903
6904       case IcsPlayingBlack:
6905         /* User is moving for Black */
6906         if (WhiteOnMove(currentMove)) {
6907             if (!appData.premove) {
6908                 DisplayMoveError(_("It is White's turn"));
6909             } else if (toX >= 0 && toY >= 0) {
6910                 premoveToX = toX;
6911                 premoveToY = toY;
6912                 premoveFromX = fromX;
6913                 premoveFromY = fromY;
6914                 premovePromoChar = promoChar;
6915                 gotPremove = 1;
6916                 if (appData.debugMode)
6917                     fprintf(debugFP, "Got premove: fromX %d,"
6918                             "fromY %d, toX %d, toY %d\n",
6919                             fromX, fromY, toX, toY);
6920             }
6921             return;
6922         }
6923         break;
6924
6925       case IcsPlayingWhite:
6926         /* User is moving for White */
6927         if (!WhiteOnMove(currentMove)) {
6928             if (!appData.premove) {
6929                 DisplayMoveError(_("It is Black's turn"));
6930             } else if (toX >= 0 && toY >= 0) {
6931                 premoveToX = toX;
6932                 premoveToY = toY;
6933                 premoveFromX = fromX;
6934                 premoveFromY = fromY;
6935                 premovePromoChar = promoChar;
6936                 gotPremove = 1;
6937                 if (appData.debugMode)
6938                     fprintf(debugFP, "Got premove: fromX %d,"
6939                             "fromY %d, toX %d, toY %d\n",
6940                             fromX, fromY, toX, toY);
6941             }
6942             return;
6943         }
6944         break;
6945
6946       default:
6947         break;
6948
6949       case EditPosition:
6950         /* EditPosition, empty square, or different color piece;
6951            click-click move is possible */
6952         if (toX == -2 || toY == -2) {
6953             boards[0][fromY][fromX] = EmptySquare;
6954             DrawPosition(FALSE, boards[currentMove]);
6955             return;
6956         } else if (toX >= 0 && toY >= 0) {
6957             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
6958                 ChessSquare q, p = boards[0][rf][ff];
6959                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
6960                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
6961                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
6962                 if(PieceToChar(q) == '+') gatingPiece = p;
6963             }
6964             boards[0][toY][toX] = boards[0][fromY][fromX];
6965             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6966                 if(boards[0][fromY][0] != EmptySquare) {
6967                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6968                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6969                 }
6970             } else
6971             if(fromX == BOARD_RGHT+1) {
6972                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6973                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6974                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6975                 }
6976             } else
6977             boards[0][fromY][fromX] = gatingPiece;
6978             DrawPosition(FALSE, boards[currentMove]);
6979             return;
6980         }
6981         return;
6982     }
6983
6984     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
6985     pup = boards[currentMove][toY][toX];
6986
6987     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6988     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6989          if( pup != EmptySquare ) return;
6990          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6991            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6992                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6993            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6994            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6995            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6996            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6997          fromY = DROP_RANK;
6998     }
6999
7000     /* [HGM] always test for legality, to get promotion info */
7001     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7002                                          fromY, fromX, toY, toX, promoChar);
7003
7004     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7005
7006     /* [HGM] but possibly ignore an IllegalMove result */
7007     if (appData.testLegality) {
7008         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7009             DisplayMoveError(_("Illegal move"));
7010             return;
7011         }
7012     }
7013
7014     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7015         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7016              ClearPremoveHighlights(); // was included
7017         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7018         return;
7019     }
7020
7021     if(addToBookFlag) { // adding moves to book
7022         char buf[MSG_SIZ], move[MSG_SIZ];
7023         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7024         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7025         AddBookMove(buf);
7026         addToBookFlag = FALSE;
7027         ClearHighlights();
7028         return;
7029     }
7030
7031     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7032 }
7033
7034 /* Common tail of UserMoveEvent and DropMenuEvent */
7035 int
7036 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7037 {
7038     char *bookHit = 0;
7039
7040     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7041         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7042         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7043         if(WhiteOnMove(currentMove)) {
7044             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7045         } else {
7046             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7047         }
7048     }
7049
7050     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7051        move type in caller when we know the move is a legal promotion */
7052     if(moveType == NormalMove && promoChar)
7053         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7054
7055     /* [HGM] <popupFix> The following if has been moved here from
7056        UserMoveEvent(). Because it seemed to belong here (why not allow
7057        piece drops in training games?), and because it can only be
7058        performed after it is known to what we promote. */
7059     if (gameMode == Training) {
7060       /* compare the move played on the board to the next move in the
7061        * game. If they match, display the move and the opponent's response.
7062        * If they don't match, display an error message.
7063        */
7064       int saveAnimate;
7065       Board testBoard;
7066       CopyBoard(testBoard, boards[currentMove]);
7067       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7068
7069       if (CompareBoards(testBoard, boards[currentMove+1])) {
7070         ForwardInner(currentMove+1);
7071
7072         /* Autoplay the opponent's response.
7073          * if appData.animate was TRUE when Training mode was entered,
7074          * the response will be animated.
7075          */
7076         saveAnimate = appData.animate;
7077         appData.animate = animateTraining;
7078         ForwardInner(currentMove+1);
7079         appData.animate = saveAnimate;
7080
7081         /* check for the end of the game */
7082         if (currentMove >= forwardMostMove) {
7083           gameMode = PlayFromGameFile;
7084           ModeHighlight();
7085           SetTrainingModeOff();
7086           DisplayInformation(_("End of game"));
7087         }
7088       } else {
7089         DisplayError(_("Incorrect move"), 0);
7090       }
7091       return 1;
7092     }
7093
7094   /* Ok, now we know that the move is good, so we can kill
7095      the previous line in Analysis Mode */
7096   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7097                                 && currentMove < forwardMostMove) {
7098     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7099     else forwardMostMove = currentMove;
7100   }
7101
7102   ClearMap();
7103
7104   /* If we need the chess program but it's dead, restart it */
7105   ResurrectChessProgram();
7106
7107   /* A user move restarts a paused game*/
7108   if (pausing)
7109     PauseEvent();
7110
7111   thinkOutput[0] = NULLCHAR;
7112
7113   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7114
7115   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7116     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7117     return 1;
7118   }
7119
7120   if (gameMode == BeginningOfGame) {
7121     if (appData.noChessProgram) {
7122       gameMode = EditGame;
7123       SetGameInfo();
7124     } else {
7125       char buf[MSG_SIZ];
7126       gameMode = MachinePlaysBlack;
7127       StartClocks();
7128       SetGameInfo();
7129       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7130       DisplayTitle(buf);
7131       if (first.sendName) {
7132         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7133         SendToProgram(buf, &first);
7134       }
7135       StartClocks();
7136     }
7137     ModeHighlight();
7138   }
7139
7140   /* Relay move to ICS or chess engine */
7141   if (appData.icsActive) {
7142     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7143         gameMode == IcsExamining) {
7144       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7145         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7146         SendToICS("draw ");
7147         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7148       }
7149       // also send plain move, in case ICS does not understand atomic claims
7150       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7151       ics_user_moved = 1;
7152     }
7153   } else {
7154     if (first.sendTime && (gameMode == BeginningOfGame ||
7155                            gameMode == MachinePlaysWhite ||
7156                            gameMode == MachinePlaysBlack)) {
7157       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7158     }
7159     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7160          // [HGM] book: if program might be playing, let it use book
7161         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7162         first.maybeThinking = TRUE;
7163     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7164         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7165         SendBoard(&first, currentMove+1);
7166         if(second.analyzing) {
7167             if(!second.useSetboard) SendToProgram("undo\n", &second);
7168             SendBoard(&second, currentMove+1);
7169         }
7170     } else {
7171         SendMoveToProgram(forwardMostMove-1, &first);
7172         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7173     }
7174     if (currentMove == cmailOldMove + 1) {
7175       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7176     }
7177   }
7178
7179   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7180
7181   switch (gameMode) {
7182   case EditGame:
7183     if(appData.testLegality)
7184     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7185     case MT_NONE:
7186     case MT_CHECK:
7187       break;
7188     case MT_CHECKMATE:
7189     case MT_STAINMATE:
7190       if (WhiteOnMove(currentMove)) {
7191         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7192       } else {
7193         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7194       }
7195       break;
7196     case MT_STALEMATE:
7197       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7198       break;
7199     }
7200     break;
7201
7202   case MachinePlaysBlack:
7203   case MachinePlaysWhite:
7204     /* disable certain menu options while machine is thinking */
7205     SetMachineThinkingEnables();
7206     break;
7207
7208   default:
7209     break;
7210   }
7211
7212   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7213   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7214
7215   if(bookHit) { // [HGM] book: simulate book reply
7216         static char bookMove[MSG_SIZ]; // a bit generous?
7217
7218         programStats.nodes = programStats.depth = programStats.time =
7219         programStats.score = programStats.got_only_move = 0;
7220         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7221
7222         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7223         strcat(bookMove, bookHit);
7224         HandleMachineMove(bookMove, &first);
7225   }
7226   return 1;
7227 }
7228
7229 void
7230 MarkByFEN(char *fen)
7231 {
7232         int r, f;
7233         if(!appData.markers || !appData.highlightDragging) return;
7234         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7235         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7236         while(*fen) {
7237             int s = 0;
7238             marker[r][f] = 0;
7239             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7240             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7241             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7242             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7243             if(*fen == 'T') marker[r][f++] = 0; else
7244             if(*fen == 'Y') marker[r][f++] = 1; else
7245             if(*fen == 'G') marker[r][f++] = 3; else
7246             if(*fen == 'B') marker[r][f++] = 4; else
7247             if(*fen == 'C') marker[r][f++] = 5; else
7248             if(*fen == 'M') marker[r][f++] = 6; else
7249             if(*fen == 'W') marker[r][f++] = 7; else
7250             if(*fen == 'D') marker[r][f++] = 8; else
7251             if(*fen == 'R') marker[r][f++] = 2; else {
7252                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7253               f += s; fen -= s>0;
7254             }
7255             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7256             if(r < 0) break;
7257             fen++;
7258         }
7259         DrawPosition(TRUE, NULL);
7260 }
7261
7262 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7263
7264 void
7265 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7266 {
7267     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7268     Markers *m = (Markers *) closure;
7269     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7270         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7271                          || kind == WhiteCapturesEnPassant
7272                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 1;
7273     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
7274 }
7275
7276 static int hoverSavedValid;
7277
7278 void
7279 MarkTargetSquares (int clear)
7280 {
7281   int x, y, sum=0;
7282   if(clear) { // no reason to ever suppress clearing
7283     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7284     hoverSavedValid = 0;
7285     if(!sum) return; // nothing was cleared,no redraw needed
7286   } else {
7287     int capt = 0;
7288     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7289        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7290     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7291     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7292       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7293       if(capt)
7294       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7295     }
7296   }
7297   DrawPosition(FALSE, NULL);
7298 }
7299
7300 int
7301 Explode (Board board, int fromX, int fromY, int toX, int toY)
7302 {
7303     if(gameInfo.variant == VariantAtomic &&
7304        (board[toY][toX] != EmptySquare ||                     // capture?
7305         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7306                          board[fromY][fromX] == BlackPawn   )
7307       )) {
7308         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7309         return TRUE;
7310     }
7311     return FALSE;
7312 }
7313
7314 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7315
7316 int
7317 CanPromote (ChessSquare piece, int y)
7318 {
7319         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7320         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7321         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7322         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7323            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7324            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7325          gameInfo.variant == VariantMakruk) return FALSE;
7326         return (piece == BlackPawn && y <= zone ||
7327                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7328                 piece == BlackLance && y == 1 ||
7329                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7330 }
7331
7332 void
7333 HoverEvent (int xPix, int yPix, int x, int y)
7334 {
7335         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7336         int r, f;
7337         if(!first.highlight) return;
7338         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7339         if(x == oldX && y == oldY) return; // only do something if we enter new square
7340         oldFromX = fromX; oldFromY = fromY;
7341         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7342           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7343             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7344           hoverSavedValid = 1;
7345         } else if(oldX != x || oldY != y) {
7346           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7347           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7348           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7349             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7350           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7351             char buf[MSG_SIZ];
7352             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7353             SendToProgram(buf, &first);
7354           }
7355           oldX = x; oldY = y;
7356 //        SetHighlights(fromX, fromY, x, y);
7357         }
7358 }
7359
7360 void ReportClick(char *action, int x, int y)
7361 {
7362         char buf[MSG_SIZ]; // Inform engine of what user does
7363         int r, f;
7364         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7365           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7366             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7367         if(!first.highlight || gameMode == EditPosition) return;
7368         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7369         SendToProgram(buf, &first);
7370 }
7371
7372 void
7373 LeftClick (ClickType clickType, int xPix, int yPix)
7374 {
7375     int x, y;
7376     Boolean saveAnimate;
7377     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7378     char promoChoice = NULLCHAR;
7379     ChessSquare piece;
7380     static TimeMark lastClickTime, prevClickTime;
7381
7382     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7383
7384     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7385
7386     if (clickType == Press) ErrorPopDown();
7387     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7388
7389     x = EventToSquare(xPix, BOARD_WIDTH);
7390     y = EventToSquare(yPix, BOARD_HEIGHT);
7391     if (!flipView && y >= 0) {
7392         y = BOARD_HEIGHT - 1 - y;
7393     }
7394     if (flipView && x >= 0) {
7395         x = BOARD_WIDTH - 1 - x;
7396     }
7397
7398     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7399         defaultPromoChoice = promoSweep;
7400         promoSweep = EmptySquare;   // terminate sweep
7401         promoDefaultAltered = TRUE;
7402         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7403     }
7404
7405     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7406         if(clickType == Release) return; // ignore upclick of click-click destination
7407         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7408         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7409         if(gameInfo.holdingsWidth &&
7410                 (WhiteOnMove(currentMove)
7411                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7412                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7413             // click in right holdings, for determining promotion piece
7414             ChessSquare p = boards[currentMove][y][x];
7415             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7416             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7417             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7418                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7419                 fromX = fromY = -1;
7420                 return;
7421             }
7422         }
7423         DrawPosition(FALSE, boards[currentMove]);
7424         return;
7425     }
7426
7427     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7428     if(clickType == Press
7429             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7430               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7431               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7432         return;
7433
7434     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7435         // could be static click on premove from-square: abort premove
7436         gotPremove = 0;
7437         ClearPremoveHighlights();
7438     }
7439
7440     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7441         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7442
7443     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7444         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7445                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7446         defaultPromoChoice = DefaultPromoChoice(side);
7447     }
7448
7449     autoQueen = appData.alwaysPromoteToQueen;
7450
7451     if (fromX == -1) {
7452       int originalY = y;
7453       gatingPiece = EmptySquare;
7454       if (clickType != Press) {
7455         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7456             DragPieceEnd(xPix, yPix); dragging = 0;
7457             DrawPosition(FALSE, NULL);
7458         }
7459         return;
7460       }
7461       doubleClick = FALSE;
7462       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7463         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7464       }
7465       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7466       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7467          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7468          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7469             /* First square */
7470             if (OKToStartUserMove(fromX, fromY)) {
7471                 second = 0;
7472                 ReportClick("lift", x, y);
7473                 MarkTargetSquares(0);
7474                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7475                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7476                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7477                     promoSweep = defaultPromoChoice;
7478                     selectFlag = 0; lastX = xPix; lastY = yPix;
7479                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7480                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7481                 }
7482                 if (appData.highlightDragging) {
7483                     SetHighlights(fromX, fromY, -1, -1);
7484                 } else {
7485                     ClearHighlights();
7486                 }
7487             } else fromX = fromY = -1;
7488             return;
7489         }
7490     }
7491
7492     /* fromX != -1 */
7493     if (clickType == Press && gameMode != EditPosition) {
7494         ChessSquare fromP;
7495         ChessSquare toP;
7496         int frc;
7497
7498         // ignore off-board to clicks
7499         if(y < 0 || x < 0) return;
7500
7501         /* Check if clicking again on the same color piece */
7502         fromP = boards[currentMove][fromY][fromX];
7503         toP = boards[currentMove][y][x];
7504         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7505         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7506            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7507              WhitePawn <= toP && toP <= WhiteKing &&
7508              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7509              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7510             (BlackPawn <= fromP && fromP <= BlackKing &&
7511              BlackPawn <= toP && toP <= BlackKing &&
7512              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7513              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7514             /* Clicked again on same color piece -- changed his mind */
7515             second = (x == fromX && y == fromY);
7516             killX = killY = -1;
7517             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7518                 second = FALSE; // first double-click rather than scond click
7519                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7520             }
7521             promoDefaultAltered = FALSE;
7522             MarkTargetSquares(1);
7523            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7524             if (appData.highlightDragging) {
7525                 SetHighlights(x, y, -1, -1);
7526             } else {
7527                 ClearHighlights();
7528             }
7529             if (OKToStartUserMove(x, y)) {
7530                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7531                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7532                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7533                  gatingPiece = boards[currentMove][fromY][fromX];
7534                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7535                 fromX = x;
7536                 fromY = y; dragging = 1;
7537                 ReportClick("lift", x, y);
7538                 MarkTargetSquares(0);
7539                 DragPieceBegin(xPix, yPix, FALSE);
7540                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7541                     promoSweep = defaultPromoChoice;
7542                     selectFlag = 0; lastX = xPix; lastY = yPix;
7543                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7544                 }
7545             }
7546            }
7547            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7548            second = FALSE;
7549         }
7550         // ignore clicks on holdings
7551         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7552     }
7553
7554     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7555         DragPieceEnd(xPix, yPix); dragging = 0;
7556         if(clearFlag) {
7557             // a deferred attempt to click-click move an empty square on top of a piece
7558             boards[currentMove][y][x] = EmptySquare;
7559             ClearHighlights();
7560             DrawPosition(FALSE, boards[currentMove]);
7561             fromX = fromY = -1; clearFlag = 0;
7562             return;
7563         }
7564         if (appData.animateDragging) {
7565             /* Undo animation damage if any */
7566             DrawPosition(FALSE, NULL);
7567         }
7568         if (second || sweepSelecting) {
7569             /* Second up/down in same square; just abort move */
7570             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7571             second = sweepSelecting = 0;
7572             fromX = fromY = -1;
7573             gatingPiece = EmptySquare;
7574             MarkTargetSquares(1);
7575             ClearHighlights();
7576             gotPremove = 0;
7577             ClearPremoveHighlights();
7578         } else {
7579             /* First upclick in same square; start click-click mode */
7580             SetHighlights(x, y, -1, -1);
7581         }
7582         return;
7583     }
7584
7585     clearFlag = 0;
7586
7587     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7588        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7589         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7590         DisplayMessage(_("only marked squares are legal"),"");
7591         DrawPosition(TRUE, NULL);
7592         return; // ignore to-click
7593     }
7594
7595     /* we now have a different from- and (possibly off-board) to-square */
7596     /* Completed move */
7597     if(!sweepSelecting) {
7598         toX = x;
7599         toY = y;
7600     }
7601
7602     piece = boards[currentMove][fromY][fromX];
7603
7604     saveAnimate = appData.animate;
7605     if (clickType == Press) {
7606         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7607         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7608             // must be Edit Position mode with empty-square selected
7609             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7610             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7611             return;
7612         }
7613         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7614             return;
7615         }
7616         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7617             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7618         } else
7619         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7620         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7621           if(appData.sweepSelect) {
7622             promoSweep = defaultPromoChoice;
7623             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7624             selectFlag = 0; lastX = xPix; lastY = yPix;
7625             Sweep(0); // Pawn that is going to promote: preview promotion piece
7626             sweepSelecting = 1;
7627             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7628             MarkTargetSquares(1);
7629           }
7630           return; // promo popup appears on up-click
7631         }
7632         /* Finish clickclick move */
7633         if (appData.animate || appData.highlightLastMove) {
7634             SetHighlights(fromX, fromY, toX, toY);
7635         } else {
7636             ClearHighlights();
7637         }
7638     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7639         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7640         if (appData.animate || appData.highlightLastMove) {
7641             SetHighlights(fromX, fromY, toX, toY);
7642         } else {
7643             ClearHighlights();
7644         }
7645     } else {
7646 #if 0
7647 // [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
7648         /* Finish drag move */
7649         if (appData.highlightLastMove) {
7650             SetHighlights(fromX, fromY, toX, toY);
7651         } else {
7652             ClearHighlights();
7653         }
7654 #endif
7655         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7656         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7657           dragging *= 2;            // flag button-less dragging if we are dragging
7658           MarkTargetSquares(1);
7659           if(x == killX && y == killY) killX = killY = -1; else {
7660             killX = x; killY = y;     //remeber this square as intermediate
7661             ReportClick("put", x, y); // and inform engine
7662             ReportClick("lift", x, y);
7663             MarkTargetSquares(0);
7664             return;
7665           }
7666         }
7667         DragPieceEnd(xPix, yPix); dragging = 0;
7668         /* Don't animate move and drag both */
7669         appData.animate = FALSE;
7670     }
7671
7672     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7673     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7674         ChessSquare piece = boards[currentMove][fromY][fromX];
7675         if(gameMode == EditPosition && piece != EmptySquare &&
7676            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7677             int n;
7678
7679             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7680                 n = PieceToNumber(piece - (int)BlackPawn);
7681                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7682                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7683                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7684             } else
7685             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7686                 n = PieceToNumber(piece);
7687                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7688                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7689                 boards[currentMove][n][BOARD_WIDTH-2]++;
7690             }
7691             boards[currentMove][fromY][fromX] = EmptySquare;
7692         }
7693         ClearHighlights();
7694         fromX = fromY = -1;
7695         MarkTargetSquares(1);
7696         DrawPosition(TRUE, boards[currentMove]);
7697         return;
7698     }
7699
7700     // off-board moves should not be highlighted
7701     if(x < 0 || y < 0) ClearHighlights();
7702     else ReportClick("put", x, y);
7703
7704     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7705
7706     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7707         SetHighlights(fromX, fromY, toX, toY);
7708         MarkTargetSquares(1);
7709         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7710             // [HGM] super: promotion to captured piece selected from holdings
7711             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7712             promotionChoice = TRUE;
7713             // kludge follows to temporarily execute move on display, without promoting yet
7714             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7715             boards[currentMove][toY][toX] = p;
7716             DrawPosition(FALSE, boards[currentMove]);
7717             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7718             boards[currentMove][toY][toX] = q;
7719             DisplayMessage("Click in holdings to choose piece", "");
7720             return;
7721         }
7722         PromotionPopUp(promoChoice);
7723     } else {
7724         int oldMove = currentMove;
7725         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7726         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7727         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7728         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7729            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7730             DrawPosition(TRUE, boards[currentMove]);
7731         MarkTargetSquares(1);
7732         fromX = fromY = -1;
7733     }
7734     appData.animate = saveAnimate;
7735     if (appData.animate || appData.animateDragging) {
7736         /* Undo animation damage if needed */
7737         DrawPosition(FALSE, NULL);
7738     }
7739 }
7740
7741 int
7742 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7743 {   // front-end-free part taken out of PieceMenuPopup
7744     int whichMenu; int xSqr, ySqr;
7745
7746     if(seekGraphUp) { // [HGM] seekgraph
7747         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7748         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7749         return -2;
7750     }
7751
7752     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7753          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7754         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7755         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7756         if(action == Press)   {
7757             originalFlip = flipView;
7758             flipView = !flipView; // temporarily flip board to see game from partners perspective
7759             DrawPosition(TRUE, partnerBoard);
7760             DisplayMessage(partnerStatus, "");
7761             partnerUp = TRUE;
7762         } else if(action == Release) {
7763             flipView = originalFlip;
7764             DrawPosition(TRUE, boards[currentMove]);
7765             partnerUp = FALSE;
7766         }
7767         return -2;
7768     }
7769
7770     xSqr = EventToSquare(x, BOARD_WIDTH);
7771     ySqr = EventToSquare(y, BOARD_HEIGHT);
7772     if (action == Release) {
7773         if(pieceSweep != EmptySquare) {
7774             EditPositionMenuEvent(pieceSweep, toX, toY);
7775             pieceSweep = EmptySquare;
7776         } else UnLoadPV(); // [HGM] pv
7777     }
7778     if (action != Press) return -2; // return code to be ignored
7779     switch (gameMode) {
7780       case IcsExamining:
7781         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7782       case EditPosition:
7783         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7784         if (xSqr < 0 || ySqr < 0) return -1;
7785         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7786         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7787         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7788         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7789         NextPiece(0);
7790         return 2; // grab
7791       case IcsObserving:
7792         if(!appData.icsEngineAnalyze) return -1;
7793       case IcsPlayingWhite:
7794       case IcsPlayingBlack:
7795         if(!appData.zippyPlay) goto noZip;
7796       case AnalyzeMode:
7797       case AnalyzeFile:
7798       case MachinePlaysWhite:
7799       case MachinePlaysBlack:
7800       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7801         if (!appData.dropMenu) {
7802           LoadPV(x, y);
7803           return 2; // flag front-end to grab mouse events
7804         }
7805         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7806            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7807       case EditGame:
7808       noZip:
7809         if (xSqr < 0 || ySqr < 0) return -1;
7810         if (!appData.dropMenu || appData.testLegality &&
7811             gameInfo.variant != VariantBughouse &&
7812             gameInfo.variant != VariantCrazyhouse) return -1;
7813         whichMenu = 1; // drop menu
7814         break;
7815       default:
7816         return -1;
7817     }
7818
7819     if (((*fromX = xSqr) < 0) ||
7820         ((*fromY = ySqr) < 0)) {
7821         *fromX = *fromY = -1;
7822         return -1;
7823     }
7824     if (flipView)
7825       *fromX = BOARD_WIDTH - 1 - *fromX;
7826     else
7827       *fromY = BOARD_HEIGHT - 1 - *fromY;
7828
7829     return whichMenu;
7830 }
7831
7832 void
7833 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7834 {
7835 //    char * hint = lastHint;
7836     FrontEndProgramStats stats;
7837
7838     stats.which = cps == &first ? 0 : 1;
7839     stats.depth = cpstats->depth;
7840     stats.nodes = cpstats->nodes;
7841     stats.score = cpstats->score;
7842     stats.time = cpstats->time;
7843     stats.pv = cpstats->movelist;
7844     stats.hint = lastHint;
7845     stats.an_move_index = 0;
7846     stats.an_move_count = 0;
7847
7848     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7849         stats.hint = cpstats->move_name;
7850         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7851         stats.an_move_count = cpstats->nr_moves;
7852     }
7853
7854     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
7855
7856     SetProgramStats( &stats );
7857 }
7858
7859 void
7860 ClearEngineOutputPane (int which)
7861 {
7862     static FrontEndProgramStats dummyStats;
7863     dummyStats.which = which;
7864     dummyStats.pv = "#";
7865     SetProgramStats( &dummyStats );
7866 }
7867
7868 #define MAXPLAYERS 500
7869
7870 char *
7871 TourneyStandings (int display)
7872 {
7873     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7874     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7875     char result, *p, *names[MAXPLAYERS];
7876
7877     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7878         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7879     names[0] = p = strdup(appData.participants);
7880     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7881
7882     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7883
7884     while(result = appData.results[nr]) {
7885         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7886         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7887         wScore = bScore = 0;
7888         switch(result) {
7889           case '+': wScore = 2; break;
7890           case '-': bScore = 2; break;
7891           case '=': wScore = bScore = 1; break;
7892           case ' ':
7893           case '*': return strdup("busy"); // tourney not finished
7894         }
7895         score[w] += wScore;
7896         score[b] += bScore;
7897         games[w]++;
7898         games[b]++;
7899         nr++;
7900     }
7901     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7902     for(w=0; w<nPlayers; w++) {
7903         bScore = -1;
7904         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7905         ranking[w] = b; points[w] = bScore; score[b] = -2;
7906     }
7907     p = malloc(nPlayers*34+1);
7908     for(w=0; w<nPlayers && w<display; w++)
7909         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7910     free(names[0]);
7911     return p;
7912 }
7913
7914 void
7915 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7916 {       // count all piece types
7917         int p, f, r;
7918         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7919         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7920         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7921                 p = board[r][f];
7922                 pCnt[p]++;
7923                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7924                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7925                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7926                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7927                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7928                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7929         }
7930 }
7931
7932 int
7933 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7934 {
7935         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7936         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7937
7938         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7939         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7940         if(myPawns == 2 && nMine == 3) // KPP
7941             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7942         if(myPawns == 1 && nMine == 2) // KP
7943             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7944         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7945             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7946         if(myPawns) return FALSE;
7947         if(pCnt[WhiteRook+side])
7948             return pCnt[BlackRook-side] ||
7949                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7950                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7951                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7952         if(pCnt[WhiteCannon+side]) {
7953             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7954             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7955         }
7956         if(pCnt[WhiteKnight+side])
7957             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7958         return FALSE;
7959 }
7960
7961 int
7962 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7963 {
7964         VariantClass v = gameInfo.variant;
7965
7966         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7967         if(v == VariantShatranj) return TRUE; // always winnable through baring
7968         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7969         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7970
7971         if(v == VariantXiangqi) {
7972                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7973
7974                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7975                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7976                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7977                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7978                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7979                 if(stale) // we have at least one last-rank P plus perhaps C
7980                     return majors // KPKX
7981                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7982                 else // KCA*E*
7983                     return pCnt[WhiteFerz+side] // KCAK
7984                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7985                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7986                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7987
7988         } else if(v == VariantKnightmate) {
7989                 if(nMine == 1) return FALSE;
7990                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7991         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7992                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7993
7994                 if(nMine == 1) return FALSE; // bare King
7995                 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
7996                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7997                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7998                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7999                 if(pCnt[WhiteKnight+side])
8000                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8001                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8002                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8003                 if(nBishops)
8004                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8005                 if(pCnt[WhiteAlfil+side])
8006                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8007                 if(pCnt[WhiteWazir+side])
8008                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8009         }
8010
8011         return TRUE;
8012 }
8013
8014 int
8015 CompareWithRights (Board b1, Board b2)
8016 {
8017     int rights = 0;
8018     if(!CompareBoards(b1, b2)) return FALSE;
8019     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8020     /* compare castling rights */
8021     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8022            rights++; /* King lost rights, while rook still had them */
8023     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8024         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8025            rights++; /* but at least one rook lost them */
8026     }
8027     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8028            rights++;
8029     if( b1[CASTLING][5] != NoRights ) {
8030         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8031            rights++;
8032     }
8033     return rights == 0;
8034 }
8035
8036 int
8037 Adjudicate (ChessProgramState *cps)
8038 {       // [HGM] some adjudications useful with buggy engines
8039         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8040         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8041         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8042         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8043         int k, drop, count = 0; static int bare = 1;
8044         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8045         Boolean canAdjudicate = !appData.icsActive;
8046
8047         // most tests only when we understand the game, i.e. legality-checking on
8048             if( appData.testLegality )
8049             {   /* [HGM] Some more adjudications for obstinate engines */
8050                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8051                 static int moveCount = 6;
8052                 ChessMove result;
8053                 char *reason = NULL;
8054
8055                 /* Count what is on board. */
8056                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8057
8058                 /* Some material-based adjudications that have to be made before stalemate test */
8059                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8060                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8061                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8062                      if(canAdjudicate && appData.checkMates) {
8063                          if(engineOpponent)
8064                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8065                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8066                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8067                          return 1;
8068                      }
8069                 }
8070
8071                 /* Bare King in Shatranj (loses) or Losers (wins) */
8072                 if( nrW == 1 || nrB == 1) {
8073                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8074                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8075                      if(canAdjudicate && appData.checkMates) {
8076                          if(engineOpponent)
8077                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8078                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8079                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8080                          return 1;
8081                      }
8082                   } else
8083                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8084                   {    /* bare King */
8085                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8086                         if(canAdjudicate && appData.checkMates) {
8087                             /* but only adjudicate if adjudication enabled */
8088                             if(engineOpponent)
8089                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8090                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8091                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8092                             return 1;
8093                         }
8094                   }
8095                 } else bare = 1;
8096
8097
8098             // don't wait for engine to announce game end if we can judge ourselves
8099             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8100               case MT_CHECK:
8101                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8102                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8103                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8104                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8105                             checkCnt++;
8106                         if(checkCnt >= 2) {
8107                             reason = "Xboard adjudication: 3rd check";
8108                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8109                             break;
8110                         }
8111                     }
8112                 }
8113               case MT_NONE:
8114               default:
8115                 break;
8116               case MT_STEALMATE:
8117               case MT_STALEMATE:
8118               case MT_STAINMATE:
8119                 reason = "Xboard adjudication: Stalemate";
8120                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8121                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8122                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8123                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8124                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8125                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8126                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8127                                                                         EP_CHECKMATE : EP_WINS);
8128                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8129                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8130                 }
8131                 break;
8132               case MT_CHECKMATE:
8133                 reason = "Xboard adjudication: Checkmate";
8134                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8135                 if(gameInfo.variant == VariantShogi) {
8136                     if(forwardMostMove > backwardMostMove
8137                        && moveList[forwardMostMove-1][1] == '@'
8138                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8139                         reason = "XBoard adjudication: pawn-drop mate";
8140                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8141                     }
8142                 }
8143                 break;
8144             }
8145
8146                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8147                     case EP_STALEMATE:
8148                         result = GameIsDrawn; break;
8149                     case EP_CHECKMATE:
8150                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8151                     case EP_WINS:
8152                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8153                     default:
8154                         result = EndOfFile;
8155                 }
8156                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8157                     if(engineOpponent)
8158                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8159                     GameEnds( result, reason, GE_XBOARD );
8160                     return 1;
8161                 }
8162
8163                 /* Next absolutely insufficient mating material. */
8164                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8165                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8166                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8167
8168                      /* always flag draws, for judging claims */
8169                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8170
8171                      if(canAdjudicate && appData.materialDraws) {
8172                          /* but only adjudicate them if adjudication enabled */
8173                          if(engineOpponent) {
8174                            SendToProgram("force\n", engineOpponent); // suppress reply
8175                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8176                          }
8177                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8178                          return 1;
8179                      }
8180                 }
8181
8182                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8183                 if(gameInfo.variant == VariantXiangqi ?
8184                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8185                  : nrW + nrB == 4 &&
8186                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8187                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8188                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8189                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8190                    ) ) {
8191                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8192                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8193                           if(engineOpponent) {
8194                             SendToProgram("force\n", engineOpponent); // suppress reply
8195                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8196                           }
8197                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8198                           return 1;
8199                      }
8200                 } else moveCount = 6;
8201             }
8202
8203         // Repetition draws and 50-move rule can be applied independently of legality testing
8204
8205                 /* Check for rep-draws */
8206                 count = 0;
8207                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8208                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8209                 for(k = forwardMostMove-2;
8210                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8211                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8212                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8213                     k-=2)
8214                 {   int rights=0;
8215                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8216                         /* compare castling rights */
8217                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8218                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8219                                 rights++; /* King lost rights, while rook still had them */
8220                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8221                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8222                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8223                                    rights++; /* but at least one rook lost them */
8224                         }
8225                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8226                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8227                                 rights++;
8228                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8229                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8230                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8231                                    rights++;
8232                         }
8233                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8234                             && appData.drawRepeats > 1) {
8235                              /* adjudicate after user-specified nr of repeats */
8236                              int result = GameIsDrawn;
8237                              char *details = "XBoard adjudication: repetition draw";
8238                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8239                                 // [HGM] xiangqi: check for forbidden perpetuals
8240                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8241                                 for(m=forwardMostMove; m>k; m-=2) {
8242                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8243                                         ourPerpetual = 0; // the current mover did not always check
8244                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8245                                         hisPerpetual = 0; // the opponent did not always check
8246                                 }
8247                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8248                                                                         ourPerpetual, hisPerpetual);
8249                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8250                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8251                                     details = "Xboard adjudication: perpetual checking";
8252                                 } else
8253                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8254                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8255                                 } else
8256                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8257                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8258                                         result = BlackWins;
8259                                         details = "Xboard adjudication: repetition";
8260                                     }
8261                                 } else // it must be XQ
8262                                 // Now check for perpetual chases
8263                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8264                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8265                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8266                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8267                                         static char resdet[MSG_SIZ];
8268                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8269                                         details = resdet;
8270                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8271                                     } else
8272                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8273                                         break; // Abort repetition-checking loop.
8274                                 }
8275                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8276                              }
8277                              if(engineOpponent) {
8278                                SendToProgram("force\n", engineOpponent); // suppress reply
8279                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8280                              }
8281                              GameEnds( result, details, GE_XBOARD );
8282                              return 1;
8283                         }
8284                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8285                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8286                     }
8287                 }
8288
8289                 /* Now we test for 50-move draws. Determine ply count */
8290                 count = forwardMostMove;
8291                 /* look for last irreversble move */
8292                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8293                     count--;
8294                 /* if we hit starting position, add initial plies */
8295                 if( count == backwardMostMove )
8296                     count -= initialRulePlies;
8297                 count = forwardMostMove - count;
8298                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8299                         // adjust reversible move counter for checks in Xiangqi
8300                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8301                         if(i < backwardMostMove) i = backwardMostMove;
8302                         while(i <= forwardMostMove) {
8303                                 lastCheck = inCheck; // check evasion does not count
8304                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8305                                 if(inCheck || lastCheck) count--; // check does not count
8306                                 i++;
8307                         }
8308                 }
8309                 if( count >= 100)
8310                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8311                          /* this is used to judge if draw claims are legal */
8312                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8313                          if(engineOpponent) {
8314                            SendToProgram("force\n", engineOpponent); // suppress reply
8315                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8316                          }
8317                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8318                          return 1;
8319                 }
8320
8321                 /* if draw offer is pending, treat it as a draw claim
8322                  * when draw condition present, to allow engines a way to
8323                  * claim draws before making their move to avoid a race
8324                  * condition occurring after their move
8325                  */
8326                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8327                          char *p = NULL;
8328                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8329                              p = "Draw claim: 50-move rule";
8330                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8331                              p = "Draw claim: 3-fold repetition";
8332                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8333                              p = "Draw claim: insufficient mating material";
8334                          if( p != NULL && canAdjudicate) {
8335                              if(engineOpponent) {
8336                                SendToProgram("force\n", engineOpponent); // suppress reply
8337                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8338                              }
8339                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8340                              return 1;
8341                          }
8342                 }
8343
8344                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8345                     if(engineOpponent) {
8346                       SendToProgram("force\n", engineOpponent); // suppress reply
8347                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8348                     }
8349                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8350                     return 1;
8351                 }
8352         return 0;
8353 }
8354
8355 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8356 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8357 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8358
8359 static int
8360 BitbaseProbe ()
8361 {
8362     int pieces[10], squares[10], cnt=0, r, f, res;
8363     static int loaded;
8364     static PPROBE_EGBB probeBB;
8365     if(!appData.testLegality) return 10;
8366     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8367     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8368     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8369     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8370         ChessSquare piece = boards[forwardMostMove][r][f];
8371         int black = (piece >= BlackPawn);
8372         int type = piece - black*BlackPawn;
8373         if(piece == EmptySquare) continue;
8374         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8375         if(type == WhiteKing) type = WhiteQueen + 1;
8376         type = egbbCode[type];
8377         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8378         pieces[cnt] = type + black*6;
8379         if(++cnt > 5) return 11;
8380     }
8381     pieces[cnt] = squares[cnt] = 0;
8382     // probe EGBB
8383     if(loaded == 2) return 13; // loading failed before
8384     if(loaded == 0) {
8385         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8386         HMODULE lib;
8387         PLOAD_EGBB loadBB;
8388         loaded = 2; // prepare for failure
8389         if(!path) return 13; // no egbb installed
8390         strncpy(buf, path + 8, MSG_SIZ);
8391         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8392         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8393         lib = LoadLibrary(buf);
8394         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8395         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8396         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8397         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8398         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8399         loaded = 1; // success!
8400     }
8401     res = probeBB(forwardMostMove & 1, pieces, squares);
8402     return res > 0 ? 1 : res < 0 ? -1 : 0;
8403 }
8404
8405 char *
8406 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8407 {   // [HGM] book: this routine intercepts moves to simulate book replies
8408     char *bookHit = NULL;
8409
8410     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8411         char buf[MSG_SIZ];
8412         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8413         SendToProgram(buf, cps);
8414     }
8415     //first determine if the incoming move brings opponent into his book
8416     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8417         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8418     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8419     if(bookHit != NULL && !cps->bookSuspend) {
8420         // make sure opponent is not going to reply after receiving move to book position
8421         SendToProgram("force\n", cps);
8422         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8423     }
8424     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8425     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8426     // now arrange restart after book miss
8427     if(bookHit) {
8428         // after a book hit we never send 'go', and the code after the call to this routine
8429         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8430         char buf[MSG_SIZ], *move = bookHit;
8431         if(cps->useSAN) {
8432             int fromX, fromY, toX, toY;
8433             char promoChar;
8434             ChessMove moveType;
8435             move = buf + 30;
8436             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8437                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8438                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8439                                     PosFlags(forwardMostMove),
8440                                     fromY, fromX, toY, toX, promoChar, move);
8441             } else {
8442                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8443                 bookHit = NULL;
8444             }
8445         }
8446         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8447         SendToProgram(buf, cps);
8448         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8449     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8450         SendToProgram("go\n", cps);
8451         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8452     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8453         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8454             SendToProgram("go\n", cps);
8455         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8456     }
8457     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8458 }
8459
8460 int
8461 LoadError (char *errmess, ChessProgramState *cps)
8462 {   // unloads engine and switches back to -ncp mode if it was first
8463     if(cps->initDone) return FALSE;
8464     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8465     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8466     cps->pr = NoProc;
8467     if(cps == &first) {
8468         appData.noChessProgram = TRUE;
8469         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8470         gameMode = BeginningOfGame; ModeHighlight();
8471         SetNCPMode();
8472     }
8473     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8474     DisplayMessage("", ""); // erase waiting message
8475     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8476     return TRUE;
8477 }
8478
8479 char *savedMessage;
8480 ChessProgramState *savedState;
8481 void
8482 DeferredBookMove (void)
8483 {
8484         if(savedState->lastPing != savedState->lastPong)
8485                     ScheduleDelayedEvent(DeferredBookMove, 10);
8486         else
8487         HandleMachineMove(savedMessage, savedState);
8488 }
8489
8490 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8491 static ChessProgramState *stalledEngine;
8492 static char stashedInputMove[MSG_SIZ];
8493
8494 void
8495 HandleMachineMove (char *message, ChessProgramState *cps)
8496 {
8497     static char firstLeg[20];
8498     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8499     char realname[MSG_SIZ];
8500     int fromX, fromY, toX, toY;
8501     ChessMove moveType;
8502     char promoChar, roar;
8503     char *p, *pv=buf1;
8504     int machineWhite, oldError;
8505     char *bookHit;
8506
8507     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8508         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8509         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8510             DisplayError(_("Invalid pairing from pairing engine"), 0);
8511             return;
8512         }
8513         pairingReceived = 1;
8514         NextMatchGame();
8515         return; // Skim the pairing messages here.
8516     }
8517
8518     oldError = cps->userError; cps->userError = 0;
8519
8520 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8521     /*
8522      * Kludge to ignore BEL characters
8523      */
8524     while (*message == '\007') message++;
8525
8526     /*
8527      * [HGM] engine debug message: ignore lines starting with '#' character
8528      */
8529     if(cps->debug && *message == '#') return;
8530
8531     /*
8532      * Look for book output
8533      */
8534     if (cps == &first && bookRequested) {
8535         if (message[0] == '\t' || message[0] == ' ') {
8536             /* Part of the book output is here; append it */
8537             strcat(bookOutput, message);
8538             strcat(bookOutput, "  \n");
8539             return;
8540         } else if (bookOutput[0] != NULLCHAR) {
8541             /* All of book output has arrived; display it */
8542             char *p = bookOutput;
8543             while (*p != NULLCHAR) {
8544                 if (*p == '\t') *p = ' ';
8545                 p++;
8546             }
8547             DisplayInformation(bookOutput);
8548             bookRequested = FALSE;
8549             /* Fall through to parse the current output */
8550         }
8551     }
8552
8553     /*
8554      * Look for machine move.
8555      */
8556     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8557         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8558     {
8559         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8560             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8561             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8562             stalledEngine = cps;
8563             if(appData.ponderNextMove) { // bring opponent out of ponder
8564                 if(gameMode == TwoMachinesPlay) {
8565                     if(cps->other->pause)
8566                         PauseEngine(cps->other);
8567                     else
8568                         SendToProgram("easy\n", cps->other);
8569                 }
8570             }
8571             StopClocks();
8572             return;
8573         }
8574
8575         /* This method is only useful on engines that support ping */
8576         if (cps->lastPing != cps->lastPong) {
8577           if (gameMode == BeginningOfGame) {
8578             /* Extra move from before last new; ignore */
8579             if (appData.debugMode) {
8580                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8581             }
8582           } else {
8583             if (appData.debugMode) {
8584                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8585                         cps->which, gameMode);
8586             }
8587
8588             SendToProgram("undo\n", cps);
8589           }
8590           return;
8591         }
8592
8593         switch (gameMode) {
8594           case BeginningOfGame:
8595             /* Extra move from before last reset; ignore */
8596             if (appData.debugMode) {
8597                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8598             }
8599             return;
8600
8601           case EndOfGame:
8602           case IcsIdle:
8603           default:
8604             /* Extra move after we tried to stop.  The mode test is
8605                not a reliable way of detecting this problem, but it's
8606                the best we can do on engines that don't support ping.
8607             */
8608             if (appData.debugMode) {
8609                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8610                         cps->which, gameMode);
8611             }
8612             SendToProgram("undo\n", cps);
8613             return;
8614
8615           case MachinePlaysWhite:
8616           case IcsPlayingWhite:
8617             machineWhite = TRUE;
8618             break;
8619
8620           case MachinePlaysBlack:
8621           case IcsPlayingBlack:
8622             machineWhite = FALSE;
8623             break;
8624
8625           case TwoMachinesPlay:
8626             machineWhite = (cps->twoMachinesColor[0] == 'w');
8627             break;
8628         }
8629         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8630             if (appData.debugMode) {
8631                 fprintf(debugFP,
8632                         "Ignoring move out of turn by %s, gameMode %d"
8633                         ", forwardMost %d\n",
8634                         cps->which, gameMode, forwardMostMove);
8635             }
8636             return;
8637         }
8638
8639         if(cps->alphaRank) AlphaRank(machineMove, 4);
8640
8641         // [HGM] lion: (some very limited) support for Alien protocol
8642         killX = killY = -1;
8643         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8644             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8645             return;
8646         } else if(firstLeg[0]) { // there was a previous leg;
8647             // only support case where same piece makes two step (and don't even test that!)
8648             char buf[20], *p = machineMove+1, *q = buf+1, f;
8649             safeStrCpy(buf, machineMove, 20);
8650             while(isdigit(*q)) q++; // find start of to-square
8651             safeStrCpy(machineMove, firstLeg, 20);
8652             while(isdigit(*p)) p++;
8653             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8654             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8655             firstLeg[0] = NULLCHAR;
8656         }
8657
8658         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8659                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8660             /* Machine move could not be parsed; ignore it. */
8661           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8662                     machineMove, _(cps->which));
8663             DisplayMoveError(buf1);
8664             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8665                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8666             if (gameMode == TwoMachinesPlay) {
8667               GameEnds(machineWhite ? BlackWins : WhiteWins,
8668                        buf1, GE_XBOARD);
8669             }
8670             return;
8671         }
8672
8673         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8674         /* So we have to redo legality test with true e.p. status here,  */
8675         /* to make sure an illegal e.p. capture does not slip through,   */
8676         /* to cause a forfeit on a justified illegal-move complaint      */
8677         /* of the opponent.                                              */
8678         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8679            ChessMove moveType;
8680            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8681                              fromY, fromX, toY, toX, promoChar);
8682             if(moveType == IllegalMove) {
8683               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8684                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8685                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8686                            buf1, GE_XBOARD);
8687                 return;
8688            } else if(!appData.fischerCastling)
8689            /* [HGM] Kludge to handle engines that send FRC-style castling
8690               when they shouldn't (like TSCP-Gothic) */
8691            switch(moveType) {
8692              case WhiteASideCastleFR:
8693              case BlackASideCastleFR:
8694                toX+=2;
8695                currentMoveString[2]++;
8696                break;
8697              case WhiteHSideCastleFR:
8698              case BlackHSideCastleFR:
8699                toX--;
8700                currentMoveString[2]--;
8701                break;
8702              default: ; // nothing to do, but suppresses warning of pedantic compilers
8703            }
8704         }
8705         hintRequested = FALSE;
8706         lastHint[0] = NULLCHAR;
8707         bookRequested = FALSE;
8708         /* Program may be pondering now */
8709         cps->maybeThinking = TRUE;
8710         if (cps->sendTime == 2) cps->sendTime = 1;
8711         if (cps->offeredDraw) cps->offeredDraw--;
8712
8713         /* [AS] Save move info*/
8714         pvInfoList[ forwardMostMove ].score = programStats.score;
8715         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8716         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8717
8718         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8719
8720         /* Test suites abort the 'game' after one move */
8721         if(*appData.finger) {
8722            static FILE *f;
8723            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8724            if(!f) f = fopen(appData.finger, "w");
8725            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8726            else { DisplayFatalError("Bad output file", errno, 0); return; }
8727            free(fen);
8728            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8729         }
8730
8731         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8732         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8733             int count = 0;
8734
8735             while( count < adjudicateLossPlies ) {
8736                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8737
8738                 if( count & 1 ) {
8739                     score = -score; /* Flip score for winning side */
8740                 }
8741
8742                 if( score > appData.adjudicateLossThreshold ) {
8743                     break;
8744                 }
8745
8746                 count++;
8747             }
8748
8749             if( count >= adjudicateLossPlies ) {
8750                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8751
8752                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8753                     "Xboard adjudication",
8754                     GE_XBOARD );
8755
8756                 return;
8757             }
8758         }
8759
8760         if(Adjudicate(cps)) {
8761             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8762             return; // [HGM] adjudicate: for all automatic game ends
8763         }
8764
8765 #if ZIPPY
8766         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8767             first.initDone) {
8768           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8769                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8770                 SendToICS("draw ");
8771                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8772           }
8773           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8774           ics_user_moved = 1;
8775           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8776                 char buf[3*MSG_SIZ];
8777
8778                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8779                         programStats.score / 100.,
8780                         programStats.depth,
8781                         programStats.time / 100.,
8782                         (unsigned int)programStats.nodes,
8783                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8784                         programStats.movelist);
8785                 SendToICS(buf);
8786           }
8787         }
8788 #endif
8789
8790         /* [AS] Clear stats for next move */
8791         ClearProgramStats();
8792         thinkOutput[0] = NULLCHAR;
8793         hiddenThinkOutputState = 0;
8794
8795         bookHit = NULL;
8796         if (gameMode == TwoMachinesPlay) {
8797             /* [HGM] relaying draw offers moved to after reception of move */
8798             /* and interpreting offer as claim if it brings draw condition */
8799             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8800                 SendToProgram("draw\n", cps->other);
8801             }
8802             if (cps->other->sendTime) {
8803                 SendTimeRemaining(cps->other,
8804                                   cps->other->twoMachinesColor[0] == 'w');
8805             }
8806             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8807             if (firstMove && !bookHit) {
8808                 firstMove = FALSE;
8809                 if (cps->other->useColors) {
8810                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8811                 }
8812                 SendToProgram("go\n", cps->other);
8813             }
8814             cps->other->maybeThinking = TRUE;
8815         }
8816
8817         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8818
8819         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8820
8821         if (!pausing && appData.ringBellAfterMoves) {
8822             if(!roar) RingBell();
8823         }
8824
8825         /*
8826          * Reenable menu items that were disabled while
8827          * machine was thinking
8828          */
8829         if (gameMode != TwoMachinesPlay)
8830             SetUserThinkingEnables();
8831
8832         // [HGM] book: after book hit opponent has received move and is now in force mode
8833         // force the book reply into it, and then fake that it outputted this move by jumping
8834         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8835         if(bookHit) {
8836                 static char bookMove[MSG_SIZ]; // a bit generous?
8837
8838                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8839                 strcat(bookMove, bookHit);
8840                 message = bookMove;
8841                 cps = cps->other;
8842                 programStats.nodes = programStats.depth = programStats.time =
8843                 programStats.score = programStats.got_only_move = 0;
8844                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8845
8846                 if(cps->lastPing != cps->lastPong) {
8847                     savedMessage = message; // args for deferred call
8848                     savedState = cps;
8849                     ScheduleDelayedEvent(DeferredBookMove, 10);
8850                     return;
8851                 }
8852                 goto FakeBookMove;
8853         }
8854
8855         return;
8856     }
8857
8858     /* Set special modes for chess engines.  Later something general
8859      *  could be added here; for now there is just one kludge feature,
8860      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8861      *  when "xboard" is given as an interactive command.
8862      */
8863     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8864         cps->useSigint = FALSE;
8865         cps->useSigterm = FALSE;
8866     }
8867     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8868       ParseFeatures(message+8, cps);
8869       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8870     }
8871
8872     if (!strncmp(message, "setup ", 6) && 
8873         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8874           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8875                                         ) { // [HGM] allow first engine to define opening position
8876       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8877       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8878       *buf = NULLCHAR;
8879       if(sscanf(message, "setup (%s", buf) == 1) {
8880         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8881         ASSIGN(appData.pieceToCharTable, buf);
8882       }
8883       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8884       if(dummy >= 3) {
8885         while(message[s] && message[s++] != ' ');
8886         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8887            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8888             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8889             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8890           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8891           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8892           startedFromSetupPosition = FALSE;
8893         }
8894       }
8895       if(startedFromSetupPosition) return;
8896       ParseFEN(boards[0], &dummy, message+s, FALSE);
8897       DrawPosition(TRUE, boards[0]);
8898       CopyBoard(initialPosition, boards[0]);
8899       startedFromSetupPosition = TRUE;
8900       return;
8901     }
8902     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
8903       ChessSquare piece = WhitePawn;
8904       char *p=buf2;
8905       if(*p == '+') piece = CHUPROMOTED WhitePawn, p++;
8906       piece += CharToPiece(*p) - WhitePawn;
8907       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
8908       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
8909       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
8910       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
8911       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
8912       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
8913                                                && gameInfo.variant != VariantGreat
8914                                                && gameInfo.variant != VariantFairy    ) return;
8915       if(piece < EmptySquare) {
8916         pieceDefs = TRUE;
8917         ASSIGN(pieceDesc[piece], buf1);
8918         if(isupper(*p) && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
8919       }
8920       return;
8921     }
8922     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8923      * want this, I was asked to put it in, and obliged.
8924      */
8925     if (!strncmp(message, "setboard ", 9)) {
8926         Board initial_position;
8927
8928         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8929
8930         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8931             DisplayError(_("Bad FEN received from engine"), 0);
8932             return ;
8933         } else {
8934            Reset(TRUE, FALSE);
8935            CopyBoard(boards[0], initial_position);
8936            initialRulePlies = FENrulePlies;
8937            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8938            else gameMode = MachinePlaysBlack;
8939            DrawPosition(FALSE, boards[currentMove]);
8940         }
8941         return;
8942     }
8943
8944     /*
8945      * Look for communication commands
8946      */
8947     if (!strncmp(message, "telluser ", 9)) {
8948         if(message[9] == '\\' && message[10] == '\\')
8949             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8950         PlayTellSound();
8951         DisplayNote(message + 9);
8952         return;
8953     }
8954     if (!strncmp(message, "tellusererror ", 14)) {
8955         cps->userError = 1;
8956         if(message[14] == '\\' && message[15] == '\\')
8957             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8958         PlayTellSound();
8959         DisplayError(message + 14, 0);
8960         return;
8961     }
8962     if (!strncmp(message, "tellopponent ", 13)) {
8963       if (appData.icsActive) {
8964         if (loggedOn) {
8965           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8966           SendToICS(buf1);
8967         }
8968       } else {
8969         DisplayNote(message + 13);
8970       }
8971       return;
8972     }
8973     if (!strncmp(message, "tellothers ", 11)) {
8974       if (appData.icsActive) {
8975         if (loggedOn) {
8976           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8977           SendToICS(buf1);
8978         }
8979       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8980       return;
8981     }
8982     if (!strncmp(message, "tellall ", 8)) {
8983       if (appData.icsActive) {
8984         if (loggedOn) {
8985           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8986           SendToICS(buf1);
8987         }
8988       } else {
8989         DisplayNote(message + 8);
8990       }
8991       return;
8992     }
8993     if (strncmp(message, "warning", 7) == 0) {
8994         /* Undocumented feature, use tellusererror in new code */
8995         DisplayError(message, 0);
8996         return;
8997     }
8998     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8999         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9000         strcat(realname, " query");
9001         AskQuestion(realname, buf2, buf1, cps->pr);
9002         return;
9003     }
9004     /* Commands from the engine directly to ICS.  We don't allow these to be
9005      *  sent until we are logged on. Crafty kibitzes have been known to
9006      *  interfere with the login process.
9007      */
9008     if (loggedOn) {
9009         if (!strncmp(message, "tellics ", 8)) {
9010             SendToICS(message + 8);
9011             SendToICS("\n");
9012             return;
9013         }
9014         if (!strncmp(message, "tellicsnoalias ", 15)) {
9015             SendToICS(ics_prefix);
9016             SendToICS(message + 15);
9017             SendToICS("\n");
9018             return;
9019         }
9020         /* The following are for backward compatibility only */
9021         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9022             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9023             SendToICS(ics_prefix);
9024             SendToICS(message);
9025             SendToICS("\n");
9026             return;
9027         }
9028     }
9029     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9030         if(initPing == cps->lastPong) {
9031             if(gameInfo.variant == VariantUnknown) {
9032                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9033                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9034                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9035             }
9036             initPing = -1;
9037         }
9038         return;
9039     }
9040     if(!strncmp(message, "highlight ", 10)) {
9041         if(appData.testLegality && appData.markers) return;
9042         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9043         return;
9044     }
9045     if(!strncmp(message, "click ", 6)) {
9046         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9047         if(appData.testLegality || !appData.oneClick) return;
9048         sscanf(message+6, "%c%d%c", &f, &y, &c);
9049         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9050         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9051         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9052         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9053         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9054         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9055             LeftClick(Release, lastLeftX, lastLeftY);
9056         controlKey  = (c == ',');
9057         LeftClick(Press, x, y);
9058         LeftClick(Release, x, y);
9059         first.highlight = f;
9060         return;
9061     }
9062     /*
9063      * If the move is illegal, cancel it and redraw the board.
9064      * Also deal with other error cases.  Matching is rather loose
9065      * here to accommodate engines written before the spec.
9066      */
9067     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9068         strncmp(message, "Error", 5) == 0) {
9069         if (StrStr(message, "name") ||
9070             StrStr(message, "rating") || StrStr(message, "?") ||
9071             StrStr(message, "result") || StrStr(message, "board") ||
9072             StrStr(message, "bk") || StrStr(message, "computer") ||
9073             StrStr(message, "variant") || StrStr(message, "hint") ||
9074             StrStr(message, "random") || StrStr(message, "depth") ||
9075             StrStr(message, "accepted")) {
9076             return;
9077         }
9078         if (StrStr(message, "protover")) {
9079           /* Program is responding to input, so it's apparently done
9080              initializing, and this error message indicates it is
9081              protocol version 1.  So we don't need to wait any longer
9082              for it to initialize and send feature commands. */
9083           FeatureDone(cps, 1);
9084           cps->protocolVersion = 1;
9085           return;
9086         }
9087         cps->maybeThinking = FALSE;
9088
9089         if (StrStr(message, "draw")) {
9090             /* Program doesn't have "draw" command */
9091             cps->sendDrawOffers = 0;
9092             return;
9093         }
9094         if (cps->sendTime != 1 &&
9095             (StrStr(message, "time") || StrStr(message, "otim"))) {
9096           /* Program apparently doesn't have "time" or "otim" command */
9097           cps->sendTime = 0;
9098           return;
9099         }
9100         if (StrStr(message, "analyze")) {
9101             cps->analysisSupport = FALSE;
9102             cps->analyzing = FALSE;
9103 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9104             EditGameEvent(); // [HGM] try to preserve loaded game
9105             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9106             DisplayError(buf2, 0);
9107             return;
9108         }
9109         if (StrStr(message, "(no matching move)st")) {
9110           /* Special kludge for GNU Chess 4 only */
9111           cps->stKludge = TRUE;
9112           SendTimeControl(cps, movesPerSession, timeControl,
9113                           timeIncrement, appData.searchDepth,
9114                           searchTime);
9115           return;
9116         }
9117         if (StrStr(message, "(no matching move)sd")) {
9118           /* Special kludge for GNU Chess 4 only */
9119           cps->sdKludge = TRUE;
9120           SendTimeControl(cps, movesPerSession, timeControl,
9121                           timeIncrement, appData.searchDepth,
9122                           searchTime);
9123           return;
9124         }
9125         if (!StrStr(message, "llegal")) {
9126             return;
9127         }
9128         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9129             gameMode == IcsIdle) return;
9130         if (forwardMostMove <= backwardMostMove) return;
9131         if (pausing) PauseEvent();
9132       if(appData.forceIllegal) {
9133             // [HGM] illegal: machine refused move; force position after move into it
9134           SendToProgram("force\n", cps);
9135           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9136                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9137                 // when black is to move, while there might be nothing on a2 or black
9138                 // might already have the move. So send the board as if white has the move.
9139                 // But first we must change the stm of the engine, as it refused the last move
9140                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9141                 if(WhiteOnMove(forwardMostMove)) {
9142                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9143                     SendBoard(cps, forwardMostMove); // kludgeless board
9144                 } else {
9145                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9146                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9147                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9148                 }
9149           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9150             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9151                  gameMode == TwoMachinesPlay)
9152               SendToProgram("go\n", cps);
9153             return;
9154       } else
9155         if (gameMode == PlayFromGameFile) {
9156             /* Stop reading this game file */
9157             gameMode = EditGame;
9158             ModeHighlight();
9159         }
9160         /* [HGM] illegal-move claim should forfeit game when Xboard */
9161         /* only passes fully legal moves                            */
9162         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9163             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9164                                 "False illegal-move claim", GE_XBOARD );
9165             return; // do not take back move we tested as valid
9166         }
9167         currentMove = forwardMostMove-1;
9168         DisplayMove(currentMove-1); /* before DisplayMoveError */
9169         SwitchClocks(forwardMostMove-1); // [HGM] race
9170         DisplayBothClocks();
9171         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9172                 parseList[currentMove], _(cps->which));
9173         DisplayMoveError(buf1);
9174         DrawPosition(FALSE, boards[currentMove]);
9175
9176         SetUserThinkingEnables();
9177         return;
9178     }
9179     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9180         /* Program has a broken "time" command that
9181            outputs a string not ending in newline.
9182            Don't use it. */
9183         cps->sendTime = 0;
9184     }
9185     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9186         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9187             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9188     }
9189
9190     /*
9191      * If chess program startup fails, exit with an error message.
9192      * Attempts to recover here are futile. [HGM] Well, we try anyway
9193      */
9194     if ((StrStr(message, "unknown host") != NULL)
9195         || (StrStr(message, "No remote directory") != NULL)
9196         || (StrStr(message, "not found") != NULL)
9197         || (StrStr(message, "No such file") != NULL)
9198         || (StrStr(message, "can't alloc") != NULL)
9199         || (StrStr(message, "Permission denied") != NULL)) {
9200
9201         cps->maybeThinking = FALSE;
9202         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9203                 _(cps->which), cps->program, cps->host, message);
9204         RemoveInputSource(cps->isr);
9205         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9206             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9207             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9208         }
9209         return;
9210     }
9211
9212     /*
9213      * Look for hint output
9214      */
9215     if (sscanf(message, "Hint: %s", buf1) == 1) {
9216         if (cps == &first && hintRequested) {
9217             hintRequested = FALSE;
9218             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9219                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9220                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9221                                     PosFlags(forwardMostMove),
9222                                     fromY, fromX, toY, toX, promoChar, buf1);
9223                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9224                 DisplayInformation(buf2);
9225             } else {
9226                 /* Hint move could not be parsed!? */
9227               snprintf(buf2, sizeof(buf2),
9228                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9229                         buf1, _(cps->which));
9230                 DisplayError(buf2, 0);
9231             }
9232         } else {
9233           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9234         }
9235         return;
9236     }
9237
9238     /*
9239      * Ignore other messages if game is not in progress
9240      */
9241     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9242         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9243
9244     /*
9245      * look for win, lose, draw, or draw offer
9246      */
9247     if (strncmp(message, "1-0", 3) == 0) {
9248         char *p, *q, *r = "";
9249         p = strchr(message, '{');
9250         if (p) {
9251             q = strchr(p, '}');
9252             if (q) {
9253                 *q = NULLCHAR;
9254                 r = p + 1;
9255             }
9256         }
9257         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9258         return;
9259     } else if (strncmp(message, "0-1", 3) == 0) {
9260         char *p, *q, *r = "";
9261         p = strchr(message, '{');
9262         if (p) {
9263             q = strchr(p, '}');
9264             if (q) {
9265                 *q = NULLCHAR;
9266                 r = p + 1;
9267             }
9268         }
9269         /* Kludge for Arasan 4.1 bug */
9270         if (strcmp(r, "Black resigns") == 0) {
9271             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9272             return;
9273         }
9274         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9275         return;
9276     } else if (strncmp(message, "1/2", 3) == 0) {
9277         char *p, *q, *r = "";
9278         p = strchr(message, '{');
9279         if (p) {
9280             q = strchr(p, '}');
9281             if (q) {
9282                 *q = NULLCHAR;
9283                 r = p + 1;
9284             }
9285         }
9286
9287         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9288         return;
9289
9290     } else if (strncmp(message, "White resign", 12) == 0) {
9291         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9292         return;
9293     } else if (strncmp(message, "Black resign", 12) == 0) {
9294         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9295         return;
9296     } else if (strncmp(message, "White matches", 13) == 0 ||
9297                strncmp(message, "Black matches", 13) == 0   ) {
9298         /* [HGM] ignore GNUShogi noises */
9299         return;
9300     } else if (strncmp(message, "White", 5) == 0 &&
9301                message[5] != '(' &&
9302                StrStr(message, "Black") == NULL) {
9303         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9304         return;
9305     } else if (strncmp(message, "Black", 5) == 0 &&
9306                message[5] != '(') {
9307         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9308         return;
9309     } else if (strcmp(message, "resign") == 0 ||
9310                strcmp(message, "computer resigns") == 0) {
9311         switch (gameMode) {
9312           case MachinePlaysBlack:
9313           case IcsPlayingBlack:
9314             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9315             break;
9316           case MachinePlaysWhite:
9317           case IcsPlayingWhite:
9318             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9319             break;
9320           case TwoMachinesPlay:
9321             if (cps->twoMachinesColor[0] == 'w')
9322               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9323             else
9324               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9325             break;
9326           default:
9327             /* can't happen */
9328             break;
9329         }
9330         return;
9331     } else if (strncmp(message, "opponent mates", 14) == 0) {
9332         switch (gameMode) {
9333           case MachinePlaysBlack:
9334           case IcsPlayingBlack:
9335             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9336             break;
9337           case MachinePlaysWhite:
9338           case IcsPlayingWhite:
9339             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9340             break;
9341           case TwoMachinesPlay:
9342             if (cps->twoMachinesColor[0] == 'w')
9343               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9344             else
9345               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9346             break;
9347           default:
9348             /* can't happen */
9349             break;
9350         }
9351         return;
9352     } else if (strncmp(message, "computer mates", 14) == 0) {
9353         switch (gameMode) {
9354           case MachinePlaysBlack:
9355           case IcsPlayingBlack:
9356             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9357             break;
9358           case MachinePlaysWhite:
9359           case IcsPlayingWhite:
9360             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9361             break;
9362           case TwoMachinesPlay:
9363             if (cps->twoMachinesColor[0] == 'w')
9364               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9365             else
9366               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9367             break;
9368           default:
9369             /* can't happen */
9370             break;
9371         }
9372         return;
9373     } else if (strncmp(message, "checkmate", 9) == 0) {
9374         if (WhiteOnMove(forwardMostMove)) {
9375             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9376         } else {
9377             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9378         }
9379         return;
9380     } else if (strstr(message, "Draw") != NULL ||
9381                strstr(message, "game is a draw") != NULL) {
9382         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9383         return;
9384     } else if (strstr(message, "offer") != NULL &&
9385                strstr(message, "draw") != NULL) {
9386 #if ZIPPY
9387         if (appData.zippyPlay && first.initDone) {
9388             /* Relay offer to ICS */
9389             SendToICS(ics_prefix);
9390             SendToICS("draw\n");
9391         }
9392 #endif
9393         cps->offeredDraw = 2; /* valid until this engine moves twice */
9394         if (gameMode == TwoMachinesPlay) {
9395             if (cps->other->offeredDraw) {
9396                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9397             /* [HGM] in two-machine mode we delay relaying draw offer      */
9398             /* until after we also have move, to see if it is really claim */
9399             }
9400         } else if (gameMode == MachinePlaysWhite ||
9401                    gameMode == MachinePlaysBlack) {
9402           if (userOfferedDraw) {
9403             DisplayInformation(_("Machine accepts your draw offer"));
9404             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9405           } else {
9406             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9407           }
9408         }
9409     }
9410
9411
9412     /*
9413      * Look for thinking output
9414      */
9415     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9416           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9417                                 ) {
9418         int plylev, mvleft, mvtot, curscore, time;
9419         char mvname[MOVE_LEN];
9420         u64 nodes; // [DM]
9421         char plyext;
9422         int ignore = FALSE;
9423         int prefixHint = FALSE;
9424         mvname[0] = NULLCHAR;
9425
9426         switch (gameMode) {
9427           case MachinePlaysBlack:
9428           case IcsPlayingBlack:
9429             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9430             break;
9431           case MachinePlaysWhite:
9432           case IcsPlayingWhite:
9433             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9434             break;
9435           case AnalyzeMode:
9436           case AnalyzeFile:
9437             break;
9438           case IcsObserving: /* [DM] icsEngineAnalyze */
9439             if (!appData.icsEngineAnalyze) ignore = TRUE;
9440             break;
9441           case TwoMachinesPlay:
9442             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9443                 ignore = TRUE;
9444             }
9445             break;
9446           default:
9447             ignore = TRUE;
9448             break;
9449         }
9450
9451         if (!ignore) {
9452             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9453             buf1[0] = NULLCHAR;
9454             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9455                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9456
9457                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9458                     nodes += u64Const(0x100000000);
9459
9460                 if (plyext != ' ' && plyext != '\t') {
9461                     time *= 100;
9462                 }
9463
9464                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9465                 if( cps->scoreIsAbsolute &&
9466                     ( gameMode == MachinePlaysBlack ||
9467                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9468                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9469                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9470                      !WhiteOnMove(currentMove)
9471                     ) )
9472                 {
9473                     curscore = -curscore;
9474                 }
9475
9476                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9477
9478                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9479                         char buf[MSG_SIZ];
9480                         FILE *f;
9481                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9482                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9483                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9484                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9485                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9486                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9487                                 fclose(f);
9488                         }
9489                         else
9490                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9491                           DisplayError(_("failed writing PV"), 0);
9492                 }
9493
9494                 tempStats.depth = plylev;
9495                 tempStats.nodes = nodes;
9496                 tempStats.time = time;
9497                 tempStats.score = curscore;
9498                 tempStats.got_only_move = 0;
9499
9500                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9501                         int ticklen;
9502
9503                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9504                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9505                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9506                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9507                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9508                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9509                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9510                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9511                 }
9512
9513                 /* Buffer overflow protection */
9514                 if (pv[0] != NULLCHAR) {
9515                     if (strlen(pv) >= sizeof(tempStats.movelist)
9516                         && appData.debugMode) {
9517                         fprintf(debugFP,
9518                                 "PV is too long; using the first %u bytes.\n",
9519                                 (unsigned) sizeof(tempStats.movelist) - 1);
9520                     }
9521
9522                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9523                 } else {
9524                     sprintf(tempStats.movelist, " no PV\n");
9525                 }
9526
9527                 if (tempStats.seen_stat) {
9528                     tempStats.ok_to_send = 1;
9529                 }
9530
9531                 if (strchr(tempStats.movelist, '(') != NULL) {
9532                     tempStats.line_is_book = 1;
9533                     tempStats.nr_moves = 0;
9534                     tempStats.moves_left = 0;
9535                 } else {
9536                     tempStats.line_is_book = 0;
9537                 }
9538
9539                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9540                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9541
9542                 SendProgramStatsToFrontend( cps, &tempStats );
9543
9544                 /*
9545                     [AS] Protect the thinkOutput buffer from overflow... this
9546                     is only useful if buf1 hasn't overflowed first!
9547                 */
9548                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9549                          plylev,
9550                          (gameMode == TwoMachinesPlay ?
9551                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9552                          ((double) curscore) / 100.0,
9553                          prefixHint ? lastHint : "",
9554                          prefixHint ? " " : "" );
9555
9556                 if( buf1[0] != NULLCHAR ) {
9557                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9558
9559                     if( strlen(pv) > max_len ) {
9560                         if( appData.debugMode) {
9561                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9562                         }
9563                         pv[max_len+1] = '\0';
9564                     }
9565
9566                     strcat( thinkOutput, pv);
9567                 }
9568
9569                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9570                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9571                     DisplayMove(currentMove - 1);
9572                 }
9573                 return;
9574
9575             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9576                 /* crafty (9.25+) says "(only move) <move>"
9577                  * if there is only 1 legal move
9578                  */
9579                 sscanf(p, "(only move) %s", buf1);
9580                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9581                 sprintf(programStats.movelist, "%s (only move)", buf1);
9582                 programStats.depth = 1;
9583                 programStats.nr_moves = 1;
9584                 programStats.moves_left = 1;
9585                 programStats.nodes = 1;
9586                 programStats.time = 1;
9587                 programStats.got_only_move = 1;
9588
9589                 /* Not really, but we also use this member to
9590                    mean "line isn't going to change" (Crafty
9591                    isn't searching, so stats won't change) */
9592                 programStats.line_is_book = 1;
9593
9594                 SendProgramStatsToFrontend( cps, &programStats );
9595
9596                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9597                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9598                     DisplayMove(currentMove - 1);
9599                 }
9600                 return;
9601             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9602                               &time, &nodes, &plylev, &mvleft,
9603                               &mvtot, mvname) >= 5) {
9604                 /* The stat01: line is from Crafty (9.29+) in response
9605                    to the "." command */
9606                 programStats.seen_stat = 1;
9607                 cps->maybeThinking = TRUE;
9608
9609                 if (programStats.got_only_move || !appData.periodicUpdates)
9610                   return;
9611
9612                 programStats.depth = plylev;
9613                 programStats.time = time;
9614                 programStats.nodes = nodes;
9615                 programStats.moves_left = mvleft;
9616                 programStats.nr_moves = mvtot;
9617                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9618                 programStats.ok_to_send = 1;
9619                 programStats.movelist[0] = '\0';
9620
9621                 SendProgramStatsToFrontend( cps, &programStats );
9622
9623                 return;
9624
9625             } else if (strncmp(message,"++",2) == 0) {
9626                 /* Crafty 9.29+ outputs this */
9627                 programStats.got_fail = 2;
9628                 return;
9629
9630             } else if (strncmp(message,"--",2) == 0) {
9631                 /* Crafty 9.29+ outputs this */
9632                 programStats.got_fail = 1;
9633                 return;
9634
9635             } else if (thinkOutput[0] != NULLCHAR &&
9636                        strncmp(message, "    ", 4) == 0) {
9637                 unsigned message_len;
9638
9639                 p = message;
9640                 while (*p && *p == ' ') p++;
9641
9642                 message_len = strlen( p );
9643
9644                 /* [AS] Avoid buffer overflow */
9645                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9646                     strcat(thinkOutput, " ");
9647                     strcat(thinkOutput, p);
9648                 }
9649
9650                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9651                     strcat(programStats.movelist, " ");
9652                     strcat(programStats.movelist, p);
9653                 }
9654
9655                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9656                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9657                     DisplayMove(currentMove - 1);
9658                 }
9659                 return;
9660             }
9661         }
9662         else {
9663             buf1[0] = NULLCHAR;
9664
9665             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9666                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9667             {
9668                 ChessProgramStats cpstats;
9669
9670                 if (plyext != ' ' && plyext != '\t') {
9671                     time *= 100;
9672                 }
9673
9674                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9675                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9676                     curscore = -curscore;
9677                 }
9678
9679                 cpstats.depth = plylev;
9680                 cpstats.nodes = nodes;
9681                 cpstats.time = time;
9682                 cpstats.score = curscore;
9683                 cpstats.got_only_move = 0;
9684                 cpstats.movelist[0] = '\0';
9685
9686                 if (buf1[0] != NULLCHAR) {
9687                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9688                 }
9689
9690                 cpstats.ok_to_send = 0;
9691                 cpstats.line_is_book = 0;
9692                 cpstats.nr_moves = 0;
9693                 cpstats.moves_left = 0;
9694
9695                 SendProgramStatsToFrontend( cps, &cpstats );
9696             }
9697         }
9698     }
9699 }
9700
9701
9702 /* Parse a game score from the character string "game", and
9703    record it as the history of the current game.  The game
9704    score is NOT assumed to start from the standard position.
9705    The display is not updated in any way.
9706    */
9707 void
9708 ParseGameHistory (char *game)
9709 {
9710     ChessMove moveType;
9711     int fromX, fromY, toX, toY, boardIndex;
9712     char promoChar;
9713     char *p, *q;
9714     char buf[MSG_SIZ];
9715
9716     if (appData.debugMode)
9717       fprintf(debugFP, "Parsing game history: %s\n", game);
9718
9719     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9720     gameInfo.site = StrSave(appData.icsHost);
9721     gameInfo.date = PGNDate();
9722     gameInfo.round = StrSave("-");
9723
9724     /* Parse out names of players */
9725     while (*game == ' ') game++;
9726     p = buf;
9727     while (*game != ' ') *p++ = *game++;
9728     *p = NULLCHAR;
9729     gameInfo.white = StrSave(buf);
9730     while (*game == ' ') game++;
9731     p = buf;
9732     while (*game != ' ' && *game != '\n') *p++ = *game++;
9733     *p = NULLCHAR;
9734     gameInfo.black = StrSave(buf);
9735
9736     /* Parse moves */
9737     boardIndex = blackPlaysFirst ? 1 : 0;
9738     yynewstr(game);
9739     for (;;) {
9740         yyboardindex = boardIndex;
9741         moveType = (ChessMove) Myylex();
9742         switch (moveType) {
9743           case IllegalMove:             /* maybe suicide chess, etc. */
9744   if (appData.debugMode) {
9745     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9746     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9747     setbuf(debugFP, NULL);
9748   }
9749           case WhitePromotion:
9750           case BlackPromotion:
9751           case WhiteNonPromotion:
9752           case BlackNonPromotion:
9753           case NormalMove:
9754           case FirstLeg:
9755           case WhiteCapturesEnPassant:
9756           case BlackCapturesEnPassant:
9757           case WhiteKingSideCastle:
9758           case WhiteQueenSideCastle:
9759           case BlackKingSideCastle:
9760           case BlackQueenSideCastle:
9761           case WhiteKingSideCastleWild:
9762           case WhiteQueenSideCastleWild:
9763           case BlackKingSideCastleWild:
9764           case BlackQueenSideCastleWild:
9765           /* PUSH Fabien */
9766           case WhiteHSideCastleFR:
9767           case WhiteASideCastleFR:
9768           case BlackHSideCastleFR:
9769           case BlackASideCastleFR:
9770           /* POP Fabien */
9771             fromX = currentMoveString[0] - AAA;
9772             fromY = currentMoveString[1] - ONE;
9773             toX = currentMoveString[2] - AAA;
9774             toY = currentMoveString[3] - ONE;
9775             promoChar = currentMoveString[4];
9776             break;
9777           case WhiteDrop:
9778           case BlackDrop:
9779             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9780             fromX = moveType == WhiteDrop ?
9781               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9782             (int) CharToPiece(ToLower(currentMoveString[0]));
9783             fromY = DROP_RANK;
9784             toX = currentMoveString[2] - AAA;
9785             toY = currentMoveString[3] - ONE;
9786             promoChar = NULLCHAR;
9787             break;
9788           case AmbiguousMove:
9789             /* bug? */
9790             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9791   if (appData.debugMode) {
9792     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9793     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9794     setbuf(debugFP, NULL);
9795   }
9796             DisplayError(buf, 0);
9797             return;
9798           case ImpossibleMove:
9799             /* bug? */
9800             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9801   if (appData.debugMode) {
9802     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9803     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9804     setbuf(debugFP, NULL);
9805   }
9806             DisplayError(buf, 0);
9807             return;
9808           case EndOfFile:
9809             if (boardIndex < backwardMostMove) {
9810                 /* Oops, gap.  How did that happen? */
9811                 DisplayError(_("Gap in move list"), 0);
9812                 return;
9813             }
9814             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9815             if (boardIndex > forwardMostMove) {
9816                 forwardMostMove = boardIndex;
9817             }
9818             return;
9819           case ElapsedTime:
9820             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9821                 strcat(parseList[boardIndex-1], " ");
9822                 strcat(parseList[boardIndex-1], yy_text);
9823             }
9824             continue;
9825           case Comment:
9826           case PGNTag:
9827           case NAG:
9828           default:
9829             /* ignore */
9830             continue;
9831           case WhiteWins:
9832           case BlackWins:
9833           case GameIsDrawn:
9834           case GameUnfinished:
9835             if (gameMode == IcsExamining) {
9836                 if (boardIndex < backwardMostMove) {
9837                     /* Oops, gap.  How did that happen? */
9838                     return;
9839                 }
9840                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9841                 return;
9842             }
9843             gameInfo.result = moveType;
9844             p = strchr(yy_text, '{');
9845             if (p == NULL) p = strchr(yy_text, '(');
9846             if (p == NULL) {
9847                 p = yy_text;
9848                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9849             } else {
9850                 q = strchr(p, *p == '{' ? '}' : ')');
9851                 if (q != NULL) *q = NULLCHAR;
9852                 p++;
9853             }
9854             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9855             gameInfo.resultDetails = StrSave(p);
9856             continue;
9857         }
9858         if (boardIndex >= forwardMostMove &&
9859             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9860             backwardMostMove = blackPlaysFirst ? 1 : 0;
9861             return;
9862         }
9863         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9864                                  fromY, fromX, toY, toX, promoChar,
9865                                  parseList[boardIndex]);
9866         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9867         /* currentMoveString is set as a side-effect of yylex */
9868         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9869         strcat(moveList[boardIndex], "\n");
9870         boardIndex++;
9871         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9872         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9873           case MT_NONE:
9874           case MT_STALEMATE:
9875           default:
9876             break;
9877           case MT_CHECK:
9878             if(!IS_SHOGI(gameInfo.variant))
9879                 strcat(parseList[boardIndex - 1], "+");
9880             break;
9881           case MT_CHECKMATE:
9882           case MT_STAINMATE:
9883             strcat(parseList[boardIndex - 1], "#");
9884             break;
9885         }
9886     }
9887 }
9888
9889
9890 /* Apply a move to the given board  */
9891 void
9892 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9893 {
9894   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9895   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9896
9897     /* [HGM] compute & store e.p. status and castling rights for new position */
9898     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9899
9900       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9901       oldEP = (signed char)board[EP_STATUS];
9902       board[EP_STATUS] = EP_NONE;
9903       board[EP_FILE] = board[EP_RANK] = 100;
9904
9905   if (fromY == DROP_RANK) {
9906         /* must be first */
9907         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9908             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9909             return;
9910         }
9911         piece = board[toY][toX] = (ChessSquare) fromX;
9912   } else {
9913 //      ChessSquare victim;
9914       int i;
9915
9916       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9917 //           victim = board[killY][killX],
9918            board[killY][killX] = EmptySquare,
9919            board[EP_STATUS] = EP_CAPTURE;
9920
9921       if( board[toY][toX] != EmptySquare ) {
9922            board[EP_STATUS] = EP_CAPTURE;
9923            if( (fromX != toX || fromY != toY) && // not igui!
9924                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9925                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9926                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9927            }
9928       }
9929
9930       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9931            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9932                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9933       } else
9934       if( board[fromY][fromX] == WhitePawn ) {
9935            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9936                board[EP_STATUS] = EP_PAWN_MOVE;
9937            if( toY-fromY==2) {
9938                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = (fromY + toY)/2;
9939                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9940                         gameInfo.variant != VariantBerolina || toX < fromX)
9941                       board[EP_STATUS] = toX | berolina;
9942                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9943                         gameInfo.variant != VariantBerolina || toX > fromX)
9944                       board[EP_STATUS] = toX;
9945            }
9946       } else
9947       if( board[fromY][fromX] == BlackPawn ) {
9948            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9949                board[EP_STATUS] = EP_PAWN_MOVE;
9950            if( toY-fromY== -2) {
9951                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = (fromY + toY)/2;
9952                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9953                         gameInfo.variant != VariantBerolina || toX < fromX)
9954                       board[EP_STATUS] = toX | berolina;
9955                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9956                         gameInfo.variant != VariantBerolina || toX > fromX)
9957                       board[EP_STATUS] = toX;
9958            }
9959        }
9960
9961        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
9962        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
9963        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
9964        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
9965
9966        for(i=0; i<nrCastlingRights; i++) {
9967            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9968               board[CASTLING][i] == toX   && castlingRank[i] == toY
9969              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9970        }
9971
9972        if(gameInfo.variant == VariantSChess) { // update virginity
9973            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9974            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9975            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9976            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9977        }
9978
9979      if (fromX == toX && fromY == toY) return;
9980
9981      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9982      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9983      if(gameInfo.variant == VariantKnightmate)
9984          king += (int) WhiteUnicorn - (int) WhiteKing;
9985
9986     /* Code added by Tord: */
9987     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9988     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9989         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9990       board[EP_STATUS] = EP_NONE; // capture was fake!
9991       board[fromY][fromX] = EmptySquare;
9992       board[toY][toX] = EmptySquare;
9993       if((toX > fromX) != (piece == WhiteRook)) {
9994         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9995       } else {
9996         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9997       }
9998     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9999                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10000       board[EP_STATUS] = EP_NONE;
10001       board[fromY][fromX] = EmptySquare;
10002       board[toY][toX] = EmptySquare;
10003       if((toX > fromX) != (piece == BlackRook)) {
10004         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10005       } else {
10006         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10007       }
10008     /* End of code added by Tord */
10009
10010     } else if (board[fromY][fromX] == king
10011         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10012         && toY == fromY && toX > fromX+1) {
10013         board[fromY][fromX] = EmptySquare;
10014         board[toY][toX] = king;
10015         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
10016         board[fromY][BOARD_RGHT-1] = EmptySquare;
10017     } else if (board[fromY][fromX] == king
10018         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10019                && toY == fromY && toX < fromX-1) {
10020         board[fromY][fromX] = EmptySquare;
10021         board[toY][toX] = king;
10022         board[toY][toX+1] = board[fromY][BOARD_LEFT];
10023         board[fromY][BOARD_LEFT] = EmptySquare;
10024     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10025                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10026                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10027                ) {
10028         /* white pawn promotion */
10029         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10030         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10031             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10032         board[fromY][fromX] = EmptySquare;
10033     } else if ((fromY >= BOARD_HEIGHT>>1)
10034                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10035                && (toX != fromX)
10036                && gameInfo.variant != VariantXiangqi
10037                && gameInfo.variant != VariantBerolina
10038                && (board[fromY][fromX] == WhitePawn)
10039                && (board[toY][toX] == EmptySquare)) {
10040         board[fromY][fromX] = EmptySquare;
10041         board[toY][toX] = WhitePawn;
10042         captured = board[toY - 1][toX];
10043         board[toY - 1][toX] = EmptySquare;
10044     } else if ((fromY == BOARD_HEIGHT-4)
10045                && (toX == fromX)
10046                && gameInfo.variant == VariantBerolina
10047                && (board[fromY][fromX] == WhitePawn)
10048                && (board[toY][toX] == EmptySquare)) {
10049         board[fromY][fromX] = EmptySquare;
10050         board[toY][toX] = WhitePawn;
10051         if(oldEP & EP_BEROLIN_A) {
10052                 captured = board[fromY][fromX-1];
10053                 board[fromY][fromX-1] = EmptySquare;
10054         }else{  captured = board[fromY][fromX+1];
10055                 board[fromY][fromX+1] = EmptySquare;
10056         }
10057     } else if (board[fromY][fromX] == king
10058         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10059                && toY == fromY && toX > fromX+1) {
10060         board[fromY][fromX] = EmptySquare;
10061         board[toY][toX] = king;
10062         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
10063         board[fromY][BOARD_RGHT-1] = EmptySquare;
10064     } else if (board[fromY][fromX] == king
10065         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10066                && toY == fromY && toX < fromX-1) {
10067         board[fromY][fromX] = EmptySquare;
10068         board[toY][toX] = king;
10069         board[toY][toX+1] = board[fromY][BOARD_LEFT];
10070         board[fromY][BOARD_LEFT] = EmptySquare;
10071     } else if (fromY == 7 && fromX == 3
10072                && board[fromY][fromX] == BlackKing
10073                && toY == 7 && toX == 5) {
10074         board[fromY][fromX] = EmptySquare;
10075         board[toY][toX] = BlackKing;
10076         board[fromY][7] = EmptySquare;
10077         board[toY][4] = BlackRook;
10078     } else if (fromY == 7 && fromX == 3
10079                && board[fromY][fromX] == BlackKing
10080                && toY == 7 && toX == 1) {
10081         board[fromY][fromX] = EmptySquare;
10082         board[toY][toX] = BlackKing;
10083         board[fromY][0] = EmptySquare;
10084         board[toY][2] = BlackRook;
10085     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10086                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10087                && toY < promoRank && promoChar
10088                ) {
10089         /* black pawn promotion */
10090         board[toY][toX] = CharToPiece(ToLower(promoChar));
10091         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10092             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10093         board[fromY][fromX] = EmptySquare;
10094     } else if ((fromY < BOARD_HEIGHT>>1)
10095                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10096                && (toX != fromX)
10097                && gameInfo.variant != VariantXiangqi
10098                && gameInfo.variant != VariantBerolina
10099                && (board[fromY][fromX] == BlackPawn)
10100                && (board[toY][toX] == EmptySquare)) {
10101         board[fromY][fromX] = EmptySquare;
10102         board[toY][toX] = BlackPawn;
10103         captured = board[toY + 1][toX];
10104         board[toY + 1][toX] = EmptySquare;
10105     } else if ((fromY == 3)
10106                && (toX == fromX)
10107                && gameInfo.variant == VariantBerolina
10108                && (board[fromY][fromX] == BlackPawn)
10109                && (board[toY][toX] == EmptySquare)) {
10110         board[fromY][fromX] = EmptySquare;
10111         board[toY][toX] = BlackPawn;
10112         if(oldEP & EP_BEROLIN_A) {
10113                 captured = board[fromY][fromX-1];
10114                 board[fromY][fromX-1] = EmptySquare;
10115         }else{  captured = board[fromY][fromX+1];
10116                 board[fromY][fromX+1] = EmptySquare;
10117         }
10118     } else {
10119         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10120         board[fromY][fromX] = EmptySquare;
10121         board[toY][toX] = piece;
10122     }
10123   }
10124
10125     if (gameInfo.holdingsWidth != 0) {
10126
10127       /* !!A lot more code needs to be written to support holdings  */
10128       /* [HGM] OK, so I have written it. Holdings are stored in the */
10129       /* penultimate board files, so they are automaticlly stored   */
10130       /* in the game history.                                       */
10131       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10132                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10133         /* Delete from holdings, by decreasing count */
10134         /* and erasing image if necessary            */
10135         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10136         if(p < (int) BlackPawn) { /* white drop */
10137              p -= (int)WhitePawn;
10138                  p = PieceToNumber((ChessSquare)p);
10139              if(p >= gameInfo.holdingsSize) p = 0;
10140              if(--board[p][BOARD_WIDTH-2] <= 0)
10141                   board[p][BOARD_WIDTH-1] = EmptySquare;
10142              if((int)board[p][BOARD_WIDTH-2] < 0)
10143                         board[p][BOARD_WIDTH-2] = 0;
10144         } else {                  /* black drop */
10145              p -= (int)BlackPawn;
10146                  p = PieceToNumber((ChessSquare)p);
10147              if(p >= gameInfo.holdingsSize) p = 0;
10148              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10149                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10150              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10151                         board[BOARD_HEIGHT-1-p][1] = 0;
10152         }
10153       }
10154       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10155           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10156         /* [HGM] holdings: Add to holdings, if holdings exist */
10157         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10158                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10159                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10160         }
10161         p = (int) captured;
10162         if (p >= (int) BlackPawn) {
10163           p -= (int)BlackPawn;
10164           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10165                   /* Restore shogi-promoted piece to its original  first */
10166                   captured = (ChessSquare) (DEMOTED captured);
10167                   p = DEMOTED p;
10168           }
10169           p = PieceToNumber((ChessSquare)p);
10170           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10171           board[p][BOARD_WIDTH-2]++;
10172           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10173         } else {
10174           p -= (int)WhitePawn;
10175           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10176                   captured = (ChessSquare) (DEMOTED captured);
10177                   p = DEMOTED p;
10178           }
10179           p = PieceToNumber((ChessSquare)p);
10180           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10181           board[BOARD_HEIGHT-1-p][1]++;
10182           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10183         }
10184       }
10185     } else if (gameInfo.variant == VariantAtomic) {
10186       if (captured != EmptySquare) {
10187         int y, x;
10188         for (y = toY-1; y <= toY+1; y++) {
10189           for (x = toX-1; x <= toX+1; x++) {
10190             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10191                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10192               board[y][x] = EmptySquare;
10193             }
10194           }
10195         }
10196         board[toY][toX] = EmptySquare;
10197       }
10198     }
10199
10200     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10201         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10202     } else
10203     if(promoChar == '+') {
10204         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10205         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10206         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10207           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10208     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10209         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10210         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10211            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10212         board[toY][toX] = newPiece;
10213     }
10214     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10215                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10216         // [HGM] superchess: take promotion piece out of holdings
10217         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10218         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10219             if(!--board[k][BOARD_WIDTH-2])
10220                 board[k][BOARD_WIDTH-1] = EmptySquare;
10221         } else {
10222             if(!--board[BOARD_HEIGHT-1-k][1])
10223                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10224         }
10225     }
10226 }
10227
10228 /* Updates forwardMostMove */
10229 void
10230 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10231 {
10232     int x = toX, y = toY;
10233     char *s = parseList[forwardMostMove];
10234     ChessSquare p = boards[forwardMostMove][toY][toX];
10235 //    forwardMostMove++; // [HGM] bare: moved downstream
10236
10237     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10238     (void) CoordsToAlgebraic(boards[forwardMostMove],
10239                              PosFlags(forwardMostMove),
10240                              fromY, fromX, y, x, promoChar,
10241                              s);
10242     if(killX >= 0 && killY >= 0)
10243         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10244
10245     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10246         int timeLeft; static int lastLoadFlag=0; int king, piece;
10247         piece = boards[forwardMostMove][fromY][fromX];
10248         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10249         if(gameInfo.variant == VariantKnightmate)
10250             king += (int) WhiteUnicorn - (int) WhiteKing;
10251         if(forwardMostMove == 0) {
10252             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10253                 fprintf(serverMoves, "%s;", UserName());
10254             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10255                 fprintf(serverMoves, "%s;", second.tidy);
10256             fprintf(serverMoves, "%s;", first.tidy);
10257             if(gameMode == MachinePlaysWhite)
10258                 fprintf(serverMoves, "%s;", UserName());
10259             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10260                 fprintf(serverMoves, "%s;", second.tidy);
10261         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10262         lastLoadFlag = loadFlag;
10263         // print base move
10264         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10265         // print castling suffix
10266         if( toY == fromY && piece == king ) {
10267             if(toX-fromX > 1)
10268                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10269             if(fromX-toX >1)
10270                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10271         }
10272         // e.p. suffix
10273         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10274              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10275              boards[forwardMostMove][toY][toX] == EmptySquare
10276              && fromX != toX && fromY != toY)
10277                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10278         // promotion suffix
10279         if(promoChar != NULLCHAR) {
10280             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10281                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10282                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10283             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10284         }
10285         if(!loadFlag) {
10286                 char buf[MOVE_LEN*2], *p; int len;
10287             fprintf(serverMoves, "/%d/%d",
10288                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10289             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10290             else                      timeLeft = blackTimeRemaining/1000;
10291             fprintf(serverMoves, "/%d", timeLeft);
10292                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10293                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10294                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10295                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10296             fprintf(serverMoves, "/%s", buf);
10297         }
10298         fflush(serverMoves);
10299     }
10300
10301     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10302         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10303       return;
10304     }
10305     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10306     if (commentList[forwardMostMove+1] != NULL) {
10307         free(commentList[forwardMostMove+1]);
10308         commentList[forwardMostMove+1] = NULL;
10309     }
10310     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10311     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10312     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10313     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10314     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10315     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10316     adjustedClock = FALSE;
10317     gameInfo.result = GameUnfinished;
10318     if (gameInfo.resultDetails != NULL) {
10319         free(gameInfo.resultDetails);
10320         gameInfo.resultDetails = NULL;
10321     }
10322     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10323                               moveList[forwardMostMove - 1]);
10324     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10325       case MT_NONE:
10326       case MT_STALEMATE:
10327       default:
10328         break;
10329       case MT_CHECK:
10330         if(!IS_SHOGI(gameInfo.variant))
10331             strcat(parseList[forwardMostMove - 1], "+");
10332         break;
10333       case MT_CHECKMATE:
10334       case MT_STAINMATE:
10335         strcat(parseList[forwardMostMove - 1], "#");
10336         break;
10337     }
10338 }
10339
10340 /* Updates currentMove if not pausing */
10341 void
10342 ShowMove (int fromX, int fromY, int toX, int toY)
10343 {
10344     int instant = (gameMode == PlayFromGameFile) ?
10345         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10346     if(appData.noGUI) return;
10347     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10348         if (!instant) {
10349             if (forwardMostMove == currentMove + 1) {
10350                 AnimateMove(boards[forwardMostMove - 1],
10351                             fromX, fromY, toX, toY);
10352             }
10353         }
10354         currentMove = forwardMostMove;
10355     }
10356
10357     killX = killY = -1; // [HGM] lion: used up
10358
10359     if (instant) return;
10360
10361     DisplayMove(currentMove - 1);
10362     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10363             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10364                 SetHighlights(fromX, fromY, toX, toY);
10365             }
10366     }
10367     DrawPosition(FALSE, boards[currentMove]);
10368     DisplayBothClocks();
10369     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10370 }
10371
10372 void
10373 SendEgtPath (ChessProgramState *cps)
10374 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10375         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10376
10377         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10378
10379         while(*p) {
10380             char c, *q = name+1, *r, *s;
10381
10382             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10383             while(*p && *p != ',') *q++ = *p++;
10384             *q++ = ':'; *q = 0;
10385             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10386                 strcmp(name, ",nalimov:") == 0 ) {
10387                 // take nalimov path from the menu-changeable option first, if it is defined
10388               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10389                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10390             } else
10391             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10392                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10393                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10394                 s = r = StrStr(s, ":") + 1; // beginning of path info
10395                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10396                 c = *r; *r = 0;             // temporarily null-terminate path info
10397                     *--q = 0;               // strip of trailig ':' from name
10398                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10399                 *r = c;
10400                 SendToProgram(buf,cps);     // send egtbpath command for this format
10401             }
10402             if(*p == ',') p++; // read away comma to position for next format name
10403         }
10404 }
10405
10406 static int
10407 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10408 {
10409       int width = 8, height = 8, holdings = 0;             // most common sizes
10410       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10411       // correct the deviations default for each variant
10412       if( v == VariantXiangqi ) width = 9,  height = 10;
10413       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10414       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10415       if( v == VariantCapablanca || v == VariantCapaRandom ||
10416           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10417                                 width = 10;
10418       if( v == VariantCourier ) width = 12;
10419       if( v == VariantSuper )                            holdings = 8;
10420       if( v == VariantGreat )   width = 10,              holdings = 8;
10421       if( v == VariantSChess )                           holdings = 7;
10422       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10423       if( v == VariantChuChess) width = 10, height = 10;
10424       if( v == VariantChu )     width = 12, height = 12;
10425       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10426              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10427              holdingsSize >= 0 && holdingsSize != holdings;
10428 }
10429
10430 char variantError[MSG_SIZ];
10431
10432 char *
10433 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10434 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10435       char *p, *variant = VariantName(v);
10436       static char b[MSG_SIZ];
10437       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10438            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10439                                                holdingsSize, variant); // cook up sized variant name
10440            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10441            if(StrStr(list, b) == NULL) {
10442                // specific sized variant not known, check if general sizing allowed
10443                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10444                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10445                             boardWidth, boardHeight, holdingsSize, engine);
10446                    return NULL;
10447                }
10448                /* [HGM] here we really should compare with the maximum supported board size */
10449            }
10450       } else snprintf(b, MSG_SIZ,"%s", variant);
10451       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10452       p = StrStr(list, b);
10453       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10454       if(p == NULL) {
10455           // occurs not at all in list, or only as sub-string
10456           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10457           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10458               int l = strlen(variantError);
10459               char *q;
10460               while(p != list && p[-1] != ',') p--;
10461               q = strchr(p, ',');
10462               if(q) *q = NULLCHAR;
10463               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10464               if(q) *q= ',';
10465           }
10466           return NULL;
10467       }
10468       return b;
10469 }
10470
10471 void
10472 InitChessProgram (ChessProgramState *cps, int setup)
10473 /* setup needed to setup FRC opening position */
10474 {
10475     char buf[MSG_SIZ], *b;
10476     if (appData.noChessProgram) return;
10477     hintRequested = FALSE;
10478     bookRequested = FALSE;
10479
10480     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10481     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10482     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10483     if(cps->memSize) { /* [HGM] memory */
10484       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10485         SendToProgram(buf, cps);
10486     }
10487     SendEgtPath(cps); /* [HGM] EGT */
10488     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10489       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10490         SendToProgram(buf, cps);
10491     }
10492
10493     setboardSpoiledMachineBlack = FALSE;
10494     SendToProgram(cps->initString, cps);
10495     if (gameInfo.variant != VariantNormal &&
10496         gameInfo.variant != VariantLoadable
10497         /* [HGM] also send variant if board size non-standard */
10498         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10499
10500       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10501                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10502       if (b == NULL) {
10503         VariantClass v;
10504         char c, *q = cps->variants, *p = strchr(q, ',');
10505         if(p) *p = NULLCHAR;
10506         v = StringToVariant(q);
10507         DisplayError(variantError, 0);
10508         if(v != VariantUnknown && cps == &first) {
10509             int w, h, s;
10510             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10511                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10512             ASSIGN(appData.variant, q);
10513             Reset(TRUE, FALSE);
10514         }
10515         if(p) *p = ',';
10516         return;
10517       }
10518
10519       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10520       SendToProgram(buf, cps);
10521     }
10522     currentlyInitializedVariant = gameInfo.variant;
10523
10524     /* [HGM] send opening position in FRC to first engine */
10525     if(setup) {
10526           SendToProgram("force\n", cps);
10527           SendBoard(cps, 0);
10528           /* engine is now in force mode! Set flag to wake it up after first move. */
10529           setboardSpoiledMachineBlack = 1;
10530     }
10531
10532     if (cps->sendICS) {
10533       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10534       SendToProgram(buf, cps);
10535     }
10536     cps->maybeThinking = FALSE;
10537     cps->offeredDraw = 0;
10538     if (!appData.icsActive) {
10539         SendTimeControl(cps, movesPerSession, timeControl,
10540                         timeIncrement, appData.searchDepth,
10541                         searchTime);
10542     }
10543     if (appData.showThinking
10544         // [HGM] thinking: four options require thinking output to be sent
10545         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10546                                 ) {
10547         SendToProgram("post\n", cps);
10548     }
10549     SendToProgram("hard\n", cps);
10550     if (!appData.ponderNextMove) {
10551         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10552            it without being sure what state we are in first.  "hard"
10553            is not a toggle, so that one is OK.
10554          */
10555         SendToProgram("easy\n", cps);
10556     }
10557     if (cps->usePing) {
10558       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10559       SendToProgram(buf, cps);
10560     }
10561     cps->initDone = TRUE;
10562     ClearEngineOutputPane(cps == &second);
10563 }
10564
10565
10566 void
10567 ResendOptions (ChessProgramState *cps)
10568 { // send the stored value of the options
10569   int i;
10570   char buf[MSG_SIZ];
10571   Option *opt = cps->option;
10572   for(i=0; i<cps->nrOptions; i++, opt++) {
10573       switch(opt->type) {
10574         case Spin:
10575         case Slider:
10576         case CheckBox:
10577             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10578           break;
10579         case ComboBox:
10580           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10581           break;
10582         default:
10583             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10584           break;
10585         case Button:
10586         case SaveButton:
10587           continue;
10588       }
10589       SendToProgram(buf, cps);
10590   }
10591 }
10592
10593 void
10594 StartChessProgram (ChessProgramState *cps)
10595 {
10596     char buf[MSG_SIZ];
10597     int err;
10598
10599     if (appData.noChessProgram) return;
10600     cps->initDone = FALSE;
10601
10602     if (strcmp(cps->host, "localhost") == 0) {
10603         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10604     } else if (*appData.remoteShell == NULLCHAR) {
10605         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10606     } else {
10607         if (*appData.remoteUser == NULLCHAR) {
10608           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10609                     cps->program);
10610         } else {
10611           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10612                     cps->host, appData.remoteUser, cps->program);
10613         }
10614         err = StartChildProcess(buf, "", &cps->pr);
10615     }
10616
10617     if (err != 0) {
10618       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10619         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10620         if(cps != &first) return;
10621         appData.noChessProgram = TRUE;
10622         ThawUI();
10623         SetNCPMode();
10624 //      DisplayFatalError(buf, err, 1);
10625 //      cps->pr = NoProc;
10626 //      cps->isr = NULL;
10627         return;
10628     }
10629
10630     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10631     if (cps->protocolVersion > 1) {
10632       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10633       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10634         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10635         cps->comboCnt = 0;  //                and values of combo boxes
10636       }
10637       SendToProgram(buf, cps);
10638       if(cps->reload) ResendOptions(cps);
10639     } else {
10640       SendToProgram("xboard\n", cps);
10641     }
10642 }
10643
10644 void
10645 TwoMachinesEventIfReady P((void))
10646 {
10647   static int curMess = 0;
10648   if (first.lastPing != first.lastPong) {
10649     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10650     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10651     return;
10652   }
10653   if (second.lastPing != second.lastPong) {
10654     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10655     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10656     return;
10657   }
10658   DisplayMessage("", ""); curMess = 0;
10659   TwoMachinesEvent();
10660 }
10661
10662 char *
10663 MakeName (char *template)
10664 {
10665     time_t clock;
10666     struct tm *tm;
10667     static char buf[MSG_SIZ];
10668     char *p = buf;
10669     int i;
10670
10671     clock = time((time_t *)NULL);
10672     tm = localtime(&clock);
10673
10674     while(*p++ = *template++) if(p[-1] == '%') {
10675         switch(*template++) {
10676           case 0:   *p = 0; return buf;
10677           case 'Y': i = tm->tm_year+1900; break;
10678           case 'y': i = tm->tm_year-100; break;
10679           case 'M': i = tm->tm_mon+1; break;
10680           case 'd': i = tm->tm_mday; break;
10681           case 'h': i = tm->tm_hour; break;
10682           case 'm': i = tm->tm_min; break;
10683           case 's': i = tm->tm_sec; break;
10684           default:  i = 0;
10685         }
10686         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10687     }
10688     return buf;
10689 }
10690
10691 int
10692 CountPlayers (char *p)
10693 {
10694     int n = 0;
10695     while(p = strchr(p, '\n')) p++, n++; // count participants
10696     return n;
10697 }
10698
10699 FILE *
10700 WriteTourneyFile (char *results, FILE *f)
10701 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10702     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10703     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10704         // create a file with tournament description
10705         fprintf(f, "-participants {%s}\n", appData.participants);
10706         fprintf(f, "-seedBase %d\n", appData.seedBase);
10707         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10708         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10709         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10710         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10711         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10712         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10713         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10714         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10715         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10716         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10717         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10718         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10719         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10720         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10721         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10722         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10723         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10724         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10725         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10726         fprintf(f, "-smpCores %d\n", appData.smpCores);
10727         if(searchTime > 0)
10728                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10729         else {
10730                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10731                 fprintf(f, "-tc %s\n", appData.timeControl);
10732                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10733         }
10734         fprintf(f, "-results \"%s\"\n", results);
10735     }
10736     return f;
10737 }
10738
10739 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10740
10741 void
10742 Substitute (char *participants, int expunge)
10743 {
10744     int i, changed, changes=0, nPlayers=0;
10745     char *p, *q, *r, buf[MSG_SIZ];
10746     if(participants == NULL) return;
10747     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10748     r = p = participants; q = appData.participants;
10749     while(*p && *p == *q) {
10750         if(*p == '\n') r = p+1, nPlayers++;
10751         p++; q++;
10752     }
10753     if(*p) { // difference
10754         while(*p && *p++ != '\n');
10755         while(*q && *q++ != '\n');
10756       changed = nPlayers;
10757         changes = 1 + (strcmp(p, q) != 0);
10758     }
10759     if(changes == 1) { // a single engine mnemonic was changed
10760         q = r; while(*q) nPlayers += (*q++ == '\n');
10761         p = buf; while(*r && (*p = *r++) != '\n') p++;
10762         *p = NULLCHAR;
10763         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10764         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10765         if(mnemonic[i]) { // The substitute is valid
10766             FILE *f;
10767             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10768                 flock(fileno(f), LOCK_EX);
10769                 ParseArgsFromFile(f);
10770                 fseek(f, 0, SEEK_SET);
10771                 FREE(appData.participants); appData.participants = participants;
10772                 if(expunge) { // erase results of replaced engine
10773                     int len = strlen(appData.results), w, b, dummy;
10774                     for(i=0; i<len; i++) {
10775                         Pairing(i, nPlayers, &w, &b, &dummy);
10776                         if((w == changed || b == changed) && appData.results[i] == '*') {
10777                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10778                             fclose(f);
10779                             return;
10780                         }
10781                     }
10782                     for(i=0; i<len; i++) {
10783                         Pairing(i, nPlayers, &w, &b, &dummy);
10784                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10785                     }
10786                 }
10787                 WriteTourneyFile(appData.results, f);
10788                 fclose(f); // release lock
10789                 return;
10790             }
10791         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10792     }
10793     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10794     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10795     free(participants);
10796     return;
10797 }
10798
10799 int
10800 CheckPlayers (char *participants)
10801 {
10802         int i;
10803         char buf[MSG_SIZ], *p;
10804         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10805         while(p = strchr(participants, '\n')) {
10806             *p = NULLCHAR;
10807             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10808             if(!mnemonic[i]) {
10809                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10810                 *p = '\n';
10811                 DisplayError(buf, 0);
10812                 return 1;
10813             }
10814             *p = '\n';
10815             participants = p + 1;
10816         }
10817         return 0;
10818 }
10819
10820 int
10821 CreateTourney (char *name)
10822 {
10823         FILE *f;
10824         if(matchMode && strcmp(name, appData.tourneyFile)) {
10825              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10826         }
10827         if(name[0] == NULLCHAR) {
10828             if(appData.participants[0])
10829                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10830             return 0;
10831         }
10832         f = fopen(name, "r");
10833         if(f) { // file exists
10834             ASSIGN(appData.tourneyFile, name);
10835             ParseArgsFromFile(f); // parse it
10836         } else {
10837             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10838             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10839                 DisplayError(_("Not enough participants"), 0);
10840                 return 0;
10841             }
10842             if(CheckPlayers(appData.participants)) return 0;
10843             ASSIGN(appData.tourneyFile, name);
10844             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10845             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10846         }
10847         fclose(f);
10848         appData.noChessProgram = FALSE;
10849         appData.clockMode = TRUE;
10850         SetGNUMode();
10851         return 1;
10852 }
10853
10854 int
10855 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10856 {
10857     char buf[MSG_SIZ], *p, *q;
10858     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10859     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10860     skip = !all && group[0]; // if group requested, we start in skip mode
10861     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10862         p = names; q = buf; header = 0;
10863         while(*p && *p != '\n') *q++ = *p++;
10864         *q = 0;
10865         if(*p == '\n') p++;
10866         if(buf[0] == '#') {
10867             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10868             depth++; // we must be entering a new group
10869             if(all) continue; // suppress printing group headers when complete list requested
10870             header = 1;
10871             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10872         }
10873         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10874         if(engineList[i]) free(engineList[i]);
10875         engineList[i] = strdup(buf);
10876         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10877         if(engineMnemonic[i]) free(engineMnemonic[i]);
10878         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10879             strcat(buf, " (");
10880             sscanf(q + 8, "%s", buf + strlen(buf));
10881             strcat(buf, ")");
10882         }
10883         engineMnemonic[i] = strdup(buf);
10884         i++;
10885     }
10886     engineList[i] = engineMnemonic[i] = NULL;
10887     return i;
10888 }
10889
10890 // following implemented as macro to avoid type limitations
10891 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10892
10893 void
10894 SwapEngines (int n)
10895 {   // swap settings for first engine and other engine (so far only some selected options)
10896     int h;
10897     char *p;
10898     if(n == 0) return;
10899     SWAP(directory, p)
10900     SWAP(chessProgram, p)
10901     SWAP(isUCI, h)
10902     SWAP(hasOwnBookUCI, h)
10903     SWAP(protocolVersion, h)
10904     SWAP(reuse, h)
10905     SWAP(scoreIsAbsolute, h)
10906     SWAP(timeOdds, h)
10907     SWAP(logo, p)
10908     SWAP(pgnName, p)
10909     SWAP(pvSAN, h)
10910     SWAP(engOptions, p)
10911     SWAP(engInitString, p)
10912     SWAP(computerString, p)
10913     SWAP(features, p)
10914     SWAP(fenOverride, p)
10915     SWAP(NPS, h)
10916     SWAP(accumulateTC, h)
10917     SWAP(drawDepth, h)
10918     SWAP(host, p)
10919     SWAP(pseudo, h)
10920 }
10921
10922 int
10923 GetEngineLine (char *s, int n)
10924 {
10925     int i;
10926     char buf[MSG_SIZ];
10927     extern char *icsNames;
10928     if(!s || !*s) return 0;
10929     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10930     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10931     if(!mnemonic[i]) return 0;
10932     if(n == 11) return 1; // just testing if there was a match
10933     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10934     if(n == 1) SwapEngines(n);
10935     ParseArgsFromString(buf);
10936     if(n == 1) SwapEngines(n);
10937     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10938         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10939         ParseArgsFromString(buf);
10940     }
10941     return 1;
10942 }
10943
10944 int
10945 SetPlayer (int player, char *p)
10946 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10947     int i;
10948     char buf[MSG_SIZ], *engineName;
10949     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10950     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10951     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10952     if(mnemonic[i]) {
10953         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10954         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10955         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10956         ParseArgsFromString(buf);
10957     } else { // no engine with this nickname is installed!
10958         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10959         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10960         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10961         ModeHighlight();
10962         DisplayError(buf, 0);
10963         return 0;
10964     }
10965     free(engineName);
10966     return i;
10967 }
10968
10969 char *recentEngines;
10970
10971 void
10972 RecentEngineEvent (int nr)
10973 {
10974     int n;
10975 //    SwapEngines(1); // bump first to second
10976 //    ReplaceEngine(&second, 1); // and load it there
10977     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10978     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10979     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10980         ReplaceEngine(&first, 0);
10981         FloatToFront(&appData.recentEngineList, command[n]);
10982     }
10983 }
10984
10985 int
10986 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10987 {   // determine players from game number
10988     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10989
10990     if(appData.tourneyType == 0) {
10991         roundsPerCycle = (nPlayers - 1) | 1;
10992         pairingsPerRound = nPlayers / 2;
10993     } else if(appData.tourneyType > 0) {
10994         roundsPerCycle = nPlayers - appData.tourneyType;
10995         pairingsPerRound = appData.tourneyType;
10996     }
10997     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10998     gamesPerCycle = gamesPerRound * roundsPerCycle;
10999     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11000     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11001     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11002     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11003     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11004     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11005
11006     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11007     if(appData.roundSync) *syncInterval = gamesPerRound;
11008
11009     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11010
11011     if(appData.tourneyType == 0) {
11012         if(curPairing == (nPlayers-1)/2 ) {
11013             *whitePlayer = curRound;
11014             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11015         } else {
11016             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11017             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11018             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11019             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11020         }
11021     } else if(appData.tourneyType > 1) {
11022         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11023         *whitePlayer = curRound + appData.tourneyType;
11024     } else if(appData.tourneyType > 0) {
11025         *whitePlayer = curPairing;
11026         *blackPlayer = curRound + appData.tourneyType;
11027     }
11028
11029     // take care of white/black alternation per round.
11030     // For cycles and games this is already taken care of by default, derived from matchGame!
11031     return curRound & 1;
11032 }
11033
11034 int
11035 NextTourneyGame (int nr, int *swapColors)
11036 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11037     char *p, *q;
11038     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11039     FILE *tf;
11040     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11041     tf = fopen(appData.tourneyFile, "r");
11042     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11043     ParseArgsFromFile(tf); fclose(tf);
11044     InitTimeControls(); // TC might be altered from tourney file
11045
11046     nPlayers = CountPlayers(appData.participants); // count participants
11047     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11048     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11049
11050     if(syncInterval) {
11051         p = q = appData.results;
11052         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11053         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11054             DisplayMessage(_("Waiting for other game(s)"),"");
11055             waitingForGame = TRUE;
11056             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11057             return 0;
11058         }
11059         waitingForGame = FALSE;
11060     }
11061
11062     if(appData.tourneyType < 0) {
11063         if(nr>=0 && !pairingReceived) {
11064             char buf[1<<16];
11065             if(pairing.pr == NoProc) {
11066                 if(!appData.pairingEngine[0]) {
11067                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11068                     return 0;
11069                 }
11070                 StartChessProgram(&pairing); // starts the pairing engine
11071             }
11072             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11073             SendToProgram(buf, &pairing);
11074             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11075             SendToProgram(buf, &pairing);
11076             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11077         }
11078         pairingReceived = 0;                              // ... so we continue here
11079         *swapColors = 0;
11080         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11081         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11082         matchGame = 1; roundNr = nr / syncInterval + 1;
11083     }
11084
11085     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11086
11087     // redefine engines, engine dir, etc.
11088     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11089     if(first.pr == NoProc) {
11090       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11091       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11092     }
11093     if(second.pr == NoProc) {
11094       SwapEngines(1);
11095       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11096       SwapEngines(1);         // and make that valid for second engine by swapping
11097       InitEngine(&second, 1);
11098     }
11099     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11100     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11101     return OK;
11102 }
11103
11104 void
11105 NextMatchGame ()
11106 {   // performs game initialization that does not invoke engines, and then tries to start the game
11107     int res, firstWhite, swapColors = 0;
11108     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11109     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
11110         char buf[MSG_SIZ];
11111         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11112         if(strcmp(buf, currentDebugFile)) { // name has changed
11113             FILE *f = fopen(buf, "w");
11114             if(f) { // if opening the new file failed, just keep using the old one
11115                 ASSIGN(currentDebugFile, buf);
11116                 fclose(debugFP);
11117                 debugFP = f;
11118             }
11119             if(appData.serverFileName) {
11120                 if(serverFP) fclose(serverFP);
11121                 serverFP = fopen(appData.serverFileName, "w");
11122                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11123                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11124             }
11125         }
11126     }
11127     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11128     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11129     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11130     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11131     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11132     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11133     Reset(FALSE, first.pr != NoProc);
11134     res = LoadGameOrPosition(matchGame); // setup game
11135     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11136     if(!res) return; // abort when bad game/pos file
11137     TwoMachinesEvent();
11138 }
11139
11140 void
11141 UserAdjudicationEvent (int result)
11142 {
11143     ChessMove gameResult = GameIsDrawn;
11144
11145     if( result > 0 ) {
11146         gameResult = WhiteWins;
11147     }
11148     else if( result < 0 ) {
11149         gameResult = BlackWins;
11150     }
11151
11152     if( gameMode == TwoMachinesPlay ) {
11153         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11154     }
11155 }
11156
11157
11158 // [HGM] save: calculate checksum of game to make games easily identifiable
11159 int
11160 StringCheckSum (char *s)
11161 {
11162         int i = 0;
11163         if(s==NULL) return 0;
11164         while(*s) i = i*259 + *s++;
11165         return i;
11166 }
11167
11168 int
11169 GameCheckSum ()
11170 {
11171         int i, sum=0;
11172         for(i=backwardMostMove; i<forwardMostMove; i++) {
11173                 sum += pvInfoList[i].depth;
11174                 sum += StringCheckSum(parseList[i]);
11175                 sum += StringCheckSum(commentList[i]);
11176                 sum *= 261;
11177         }
11178         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11179         return sum + StringCheckSum(commentList[i]);
11180 } // end of save patch
11181
11182 void
11183 GameEnds (ChessMove result, char *resultDetails, int whosays)
11184 {
11185     GameMode nextGameMode;
11186     int isIcsGame;
11187     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11188
11189     if(endingGame) return; /* [HGM] crash: forbid recursion */
11190     endingGame = 1;
11191     if(twoBoards) { // [HGM] dual: switch back to one board
11192         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11193         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11194     }
11195     if (appData.debugMode) {
11196       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11197               result, resultDetails ? resultDetails : "(null)", whosays);
11198     }
11199
11200     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11201
11202     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11203
11204     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11205         /* If we are playing on ICS, the server decides when the
11206            game is over, but the engine can offer to draw, claim
11207            a draw, or resign.
11208          */
11209 #if ZIPPY
11210         if (appData.zippyPlay && first.initDone) {
11211             if (result == GameIsDrawn) {
11212                 /* In case draw still needs to be claimed */
11213                 SendToICS(ics_prefix);
11214                 SendToICS("draw\n");
11215             } else if (StrCaseStr(resultDetails, "resign")) {
11216                 SendToICS(ics_prefix);
11217                 SendToICS("resign\n");
11218             }
11219         }
11220 #endif
11221         endingGame = 0; /* [HGM] crash */
11222         return;
11223     }
11224
11225     /* If we're loading the game from a file, stop */
11226     if (whosays == GE_FILE) {
11227       (void) StopLoadGameTimer();
11228       gameFileFP = NULL;
11229     }
11230
11231     /* Cancel draw offers */
11232     first.offeredDraw = second.offeredDraw = 0;
11233
11234     /* If this is an ICS game, only ICS can really say it's done;
11235        if not, anyone can. */
11236     isIcsGame = (gameMode == IcsPlayingWhite ||
11237                  gameMode == IcsPlayingBlack ||
11238                  gameMode == IcsObserving    ||
11239                  gameMode == IcsExamining);
11240
11241     if (!isIcsGame || whosays == GE_ICS) {
11242         /* OK -- not an ICS game, or ICS said it was done */
11243         StopClocks();
11244         if (!isIcsGame && !appData.noChessProgram)
11245           SetUserThinkingEnables();
11246
11247         /* [HGM] if a machine claims the game end we verify this claim */
11248         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11249             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11250                 char claimer;
11251                 ChessMove trueResult = (ChessMove) -1;
11252
11253                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11254                                             first.twoMachinesColor[0] :
11255                                             second.twoMachinesColor[0] ;
11256
11257                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11258                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11259                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11260                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11261                 } else
11262                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11263                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11264                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11265                 } else
11266                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11267                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11268                 }
11269
11270                 // now verify win claims, but not in drop games, as we don't understand those yet
11271                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11272                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11273                     (result == WhiteWins && claimer == 'w' ||
11274                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11275                       if (appData.debugMode) {
11276                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11277                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11278                       }
11279                       if(result != trueResult) {
11280                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11281                               result = claimer == 'w' ? BlackWins : WhiteWins;
11282                               resultDetails = buf;
11283                       }
11284                 } else
11285                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11286                     && (forwardMostMove <= backwardMostMove ||
11287                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11288                         (claimer=='b')==(forwardMostMove&1))
11289                                                                                   ) {
11290                       /* [HGM] verify: draws that were not flagged are false claims */
11291                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11292                       result = claimer == 'w' ? BlackWins : WhiteWins;
11293                       resultDetails = buf;
11294                 }
11295                 /* (Claiming a loss is accepted no questions asked!) */
11296             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11297                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11298                 result = GameUnfinished;
11299                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11300             }
11301             /* [HGM] bare: don't allow bare King to win */
11302             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11303                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11304                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11305                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11306                && result != GameIsDrawn)
11307             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11308                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11309                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11310                         if(p >= 0 && p <= (int)WhiteKing) k++;
11311                 }
11312                 if (appData.debugMode) {
11313                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11314                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11315                 }
11316                 if(k <= 1) {
11317                         result = GameIsDrawn;
11318                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11319                         resultDetails = buf;
11320                 }
11321             }
11322         }
11323
11324
11325         if(serverMoves != NULL && !loadFlag) { char c = '=';
11326             if(result==WhiteWins) c = '+';
11327             if(result==BlackWins) c = '-';
11328             if(resultDetails != NULL)
11329                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11330         }
11331         if (resultDetails != NULL) {
11332             gameInfo.result = result;
11333             gameInfo.resultDetails = StrSave(resultDetails);
11334
11335             /* display last move only if game was not loaded from file */
11336             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11337                 DisplayMove(currentMove - 1);
11338
11339             if (forwardMostMove != 0) {
11340                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11341                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11342                                                                 ) {
11343                     if (*appData.saveGameFile != NULLCHAR) {
11344                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11345                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11346                         else
11347                         SaveGameToFile(appData.saveGameFile, TRUE);
11348                     } else if (appData.autoSaveGames) {
11349                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11350                     }
11351                     if (*appData.savePositionFile != NULLCHAR) {
11352                         SavePositionToFile(appData.savePositionFile);
11353                     }
11354                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11355                 }
11356             }
11357
11358             /* Tell program how game ended in case it is learning */
11359             /* [HGM] Moved this to after saving the PGN, just in case */
11360             /* engine died and we got here through time loss. In that */
11361             /* case we will get a fatal error writing the pipe, which */
11362             /* would otherwise lose us the PGN.                       */
11363             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11364             /* output during GameEnds should never be fatal anymore   */
11365             if (gameMode == MachinePlaysWhite ||
11366                 gameMode == MachinePlaysBlack ||
11367                 gameMode == TwoMachinesPlay ||
11368                 gameMode == IcsPlayingWhite ||
11369                 gameMode == IcsPlayingBlack ||
11370                 gameMode == BeginningOfGame) {
11371                 char buf[MSG_SIZ];
11372                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11373                         resultDetails);
11374                 if (first.pr != NoProc) {
11375                     SendToProgram(buf, &first);
11376                 }
11377                 if (second.pr != NoProc &&
11378                     gameMode == TwoMachinesPlay) {
11379                     SendToProgram(buf, &second);
11380                 }
11381             }
11382         }
11383
11384         if (appData.icsActive) {
11385             if (appData.quietPlay &&
11386                 (gameMode == IcsPlayingWhite ||
11387                  gameMode == IcsPlayingBlack)) {
11388                 SendToICS(ics_prefix);
11389                 SendToICS("set shout 1\n");
11390             }
11391             nextGameMode = IcsIdle;
11392             ics_user_moved = FALSE;
11393             /* clean up premove.  It's ugly when the game has ended and the
11394              * premove highlights are still on the board.
11395              */
11396             if (gotPremove) {
11397               gotPremove = FALSE;
11398               ClearPremoveHighlights();
11399               DrawPosition(FALSE, boards[currentMove]);
11400             }
11401             if (whosays == GE_ICS) {
11402                 switch (result) {
11403                 case WhiteWins:
11404                     if (gameMode == IcsPlayingWhite)
11405                         PlayIcsWinSound();
11406                     else if(gameMode == IcsPlayingBlack)
11407                         PlayIcsLossSound();
11408                     break;
11409                 case BlackWins:
11410                     if (gameMode == IcsPlayingBlack)
11411                         PlayIcsWinSound();
11412                     else if(gameMode == IcsPlayingWhite)
11413                         PlayIcsLossSound();
11414                     break;
11415                 case GameIsDrawn:
11416                     PlayIcsDrawSound();
11417                     break;
11418                 default:
11419                     PlayIcsUnfinishedSound();
11420                 }
11421             }
11422             if(appData.quitNext) { ExitEvent(0); return; }
11423         } else if (gameMode == EditGame ||
11424                    gameMode == PlayFromGameFile ||
11425                    gameMode == AnalyzeMode ||
11426                    gameMode == AnalyzeFile) {
11427             nextGameMode = gameMode;
11428         } else {
11429             nextGameMode = EndOfGame;
11430         }
11431         pausing = FALSE;
11432         ModeHighlight();
11433     } else {
11434         nextGameMode = gameMode;
11435     }
11436
11437     if (appData.noChessProgram) {
11438         gameMode = nextGameMode;
11439         ModeHighlight();
11440         endingGame = 0; /* [HGM] crash */
11441         return;
11442     }
11443
11444     if (first.reuse) {
11445         /* Put first chess program into idle state */
11446         if (first.pr != NoProc &&
11447             (gameMode == MachinePlaysWhite ||
11448              gameMode == MachinePlaysBlack ||
11449              gameMode == TwoMachinesPlay ||
11450              gameMode == IcsPlayingWhite ||
11451              gameMode == IcsPlayingBlack ||
11452              gameMode == BeginningOfGame)) {
11453             SendToProgram("force\n", &first);
11454             if (first.usePing) {
11455               char buf[MSG_SIZ];
11456               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11457               SendToProgram(buf, &first);
11458             }
11459         }
11460     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11461         /* Kill off first chess program */
11462         if (first.isr != NULL)
11463           RemoveInputSource(first.isr);
11464         first.isr = NULL;
11465
11466         if (first.pr != NoProc) {
11467             ExitAnalyzeMode();
11468             DoSleep( appData.delayBeforeQuit );
11469             SendToProgram("quit\n", &first);
11470             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11471             first.reload = TRUE;
11472         }
11473         first.pr = NoProc;
11474     }
11475     if (second.reuse) {
11476         /* Put second chess program into idle state */
11477         if (second.pr != NoProc &&
11478             gameMode == TwoMachinesPlay) {
11479             SendToProgram("force\n", &second);
11480             if (second.usePing) {
11481               char buf[MSG_SIZ];
11482               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11483               SendToProgram(buf, &second);
11484             }
11485         }
11486     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11487         /* Kill off second chess program */
11488         if (second.isr != NULL)
11489           RemoveInputSource(second.isr);
11490         second.isr = NULL;
11491
11492         if (second.pr != NoProc) {
11493             DoSleep( appData.delayBeforeQuit );
11494             SendToProgram("quit\n", &second);
11495             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11496             second.reload = TRUE;
11497         }
11498         second.pr = NoProc;
11499     }
11500
11501     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11502         char resChar = '=';
11503         switch (result) {
11504         case WhiteWins:
11505           resChar = '+';
11506           if (first.twoMachinesColor[0] == 'w') {
11507             first.matchWins++;
11508           } else {
11509             second.matchWins++;
11510           }
11511           break;
11512         case BlackWins:
11513           resChar = '-';
11514           if (first.twoMachinesColor[0] == 'b') {
11515             first.matchWins++;
11516           } else {
11517             second.matchWins++;
11518           }
11519           break;
11520         case GameUnfinished:
11521           resChar = ' ';
11522         default:
11523           break;
11524         }
11525
11526         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11527         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11528             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11529             ReserveGame(nextGame, resChar); // sets nextGame
11530             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11531             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11532         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11533
11534         if (nextGame <= appData.matchGames && !abortMatch) {
11535             gameMode = nextGameMode;
11536             matchGame = nextGame; // this will be overruled in tourney mode!
11537             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11538             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11539             endingGame = 0; /* [HGM] crash */
11540             return;
11541         } else {
11542             gameMode = nextGameMode;
11543             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11544                      first.tidy, second.tidy,
11545                      first.matchWins, second.matchWins,
11546                      appData.matchGames - (first.matchWins + second.matchWins));
11547             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11548             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11549             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11550             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11551                 first.twoMachinesColor = "black\n";
11552                 second.twoMachinesColor = "white\n";
11553             } else {
11554                 first.twoMachinesColor = "white\n";
11555                 second.twoMachinesColor = "black\n";
11556             }
11557         }
11558     }
11559     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11560         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11561       ExitAnalyzeMode();
11562     gameMode = nextGameMode;
11563     ModeHighlight();
11564     endingGame = 0;  /* [HGM] crash */
11565     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11566         if(matchMode == TRUE) { // match through command line: exit with or without popup
11567             if(ranking) {
11568                 ToNrEvent(forwardMostMove);
11569                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11570                 else ExitEvent(0);
11571             } else DisplayFatalError(buf, 0, 0);
11572         } else { // match through menu; just stop, with or without popup
11573             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11574             ModeHighlight();
11575             if(ranking){
11576                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11577             } else DisplayNote(buf);
11578       }
11579       if(ranking) free(ranking);
11580     }
11581 }
11582
11583 /* Assumes program was just initialized (initString sent).
11584    Leaves program in force mode. */
11585 void
11586 FeedMovesToProgram (ChessProgramState *cps, int upto)
11587 {
11588     int i;
11589
11590     if (appData.debugMode)
11591       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11592               startedFromSetupPosition ? "position and " : "",
11593               backwardMostMove, upto, cps->which);
11594     if(currentlyInitializedVariant != gameInfo.variant) {
11595       char buf[MSG_SIZ];
11596         // [HGM] variantswitch: make engine aware of new variant
11597         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11598                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11599                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11600         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11601         SendToProgram(buf, cps);
11602         currentlyInitializedVariant = gameInfo.variant;
11603     }
11604     SendToProgram("force\n", cps);
11605     if (startedFromSetupPosition) {
11606         SendBoard(cps, backwardMostMove);
11607     if (appData.debugMode) {
11608         fprintf(debugFP, "feedMoves\n");
11609     }
11610     }
11611     for (i = backwardMostMove; i < upto; i++) {
11612         SendMoveToProgram(i, cps);
11613     }
11614 }
11615
11616
11617 int
11618 ResurrectChessProgram ()
11619 {
11620      /* The chess program may have exited.
11621         If so, restart it and feed it all the moves made so far. */
11622     static int doInit = 0;
11623
11624     if (appData.noChessProgram) return 1;
11625
11626     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11627         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11628         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11629         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11630     } else {
11631         if (first.pr != NoProc) return 1;
11632         StartChessProgram(&first);
11633     }
11634     InitChessProgram(&first, FALSE);
11635     FeedMovesToProgram(&first, currentMove);
11636
11637     if (!first.sendTime) {
11638         /* can't tell gnuchess what its clock should read,
11639            so we bow to its notion. */
11640         ResetClocks();
11641         timeRemaining[0][currentMove] = whiteTimeRemaining;
11642         timeRemaining[1][currentMove] = blackTimeRemaining;
11643     }
11644
11645     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11646                 appData.icsEngineAnalyze) && first.analysisSupport) {
11647       SendToProgram("analyze\n", &first);
11648       first.analyzing = TRUE;
11649     }
11650     return 1;
11651 }
11652
11653 /*
11654  * Button procedures
11655  */
11656 void
11657 Reset (int redraw, int init)
11658 {
11659     int i;
11660
11661     if (appData.debugMode) {
11662         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11663                 redraw, init, gameMode);
11664     }
11665     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11666     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11667     CleanupTail(); // [HGM] vari: delete any stored variations
11668     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11669     pausing = pauseExamInvalid = FALSE;
11670     startedFromSetupPosition = blackPlaysFirst = FALSE;
11671     firstMove = TRUE;
11672     whiteFlag = blackFlag = FALSE;
11673     userOfferedDraw = FALSE;
11674     hintRequested = bookRequested = FALSE;
11675     first.maybeThinking = FALSE;
11676     second.maybeThinking = FALSE;
11677     first.bookSuspend = FALSE; // [HGM] book
11678     second.bookSuspend = FALSE;
11679     thinkOutput[0] = NULLCHAR;
11680     lastHint[0] = NULLCHAR;
11681     ClearGameInfo(&gameInfo);
11682     gameInfo.variant = StringToVariant(appData.variant);
11683     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11684     ics_user_moved = ics_clock_paused = FALSE;
11685     ics_getting_history = H_FALSE;
11686     ics_gamenum = -1;
11687     white_holding[0] = black_holding[0] = NULLCHAR;
11688     ClearProgramStats();
11689     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11690
11691     ResetFrontEnd();
11692     ClearHighlights();
11693     flipView = appData.flipView;
11694     ClearPremoveHighlights();
11695     gotPremove = FALSE;
11696     alarmSounded = FALSE;
11697     killX = killY = -1; // [HGM] lion
11698
11699     GameEnds(EndOfFile, NULL, GE_PLAYER);
11700     if(appData.serverMovesName != NULL) {
11701         /* [HGM] prepare to make moves file for broadcasting */
11702         clock_t t = clock();
11703         if(serverMoves != NULL) fclose(serverMoves);
11704         serverMoves = fopen(appData.serverMovesName, "r");
11705         if(serverMoves != NULL) {
11706             fclose(serverMoves);
11707             /* delay 15 sec before overwriting, so all clients can see end */
11708             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11709         }
11710         serverMoves = fopen(appData.serverMovesName, "w");
11711     }
11712
11713     ExitAnalyzeMode();
11714     gameMode = BeginningOfGame;
11715     ModeHighlight();
11716     if(appData.icsActive) gameInfo.variant = VariantNormal;
11717     currentMove = forwardMostMove = backwardMostMove = 0;
11718     MarkTargetSquares(1);
11719     InitPosition(redraw);
11720     for (i = 0; i < MAX_MOVES; i++) {
11721         if (commentList[i] != NULL) {
11722             free(commentList[i]);
11723             commentList[i] = NULL;
11724         }
11725     }
11726     ResetClocks();
11727     timeRemaining[0][0] = whiteTimeRemaining;
11728     timeRemaining[1][0] = blackTimeRemaining;
11729
11730     if (first.pr == NoProc) {
11731         StartChessProgram(&first);
11732     }
11733     if (init) {
11734             InitChessProgram(&first, startedFromSetupPosition);
11735     }
11736     DisplayTitle("");
11737     DisplayMessage("", "");
11738     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11739     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11740     ClearMap();        // [HGM] exclude: invalidate map
11741 }
11742
11743 void
11744 AutoPlayGameLoop ()
11745 {
11746     for (;;) {
11747         if (!AutoPlayOneMove())
11748           return;
11749         if (matchMode || appData.timeDelay == 0)
11750           continue;
11751         if (appData.timeDelay < 0)
11752           return;
11753         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11754         break;
11755     }
11756 }
11757
11758 void
11759 AnalyzeNextGame()
11760 {
11761     ReloadGame(1); // next game
11762 }
11763
11764 int
11765 AutoPlayOneMove ()
11766 {
11767     int fromX, fromY, toX, toY;
11768
11769     if (appData.debugMode) {
11770       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11771     }
11772
11773     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11774       return FALSE;
11775
11776     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11777       pvInfoList[currentMove].depth = programStats.depth;
11778       pvInfoList[currentMove].score = programStats.score;
11779       pvInfoList[currentMove].time  = 0;
11780       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11781       else { // append analysis of final position as comment
11782         char buf[MSG_SIZ];
11783         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11784         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11785       }
11786       programStats.depth = 0;
11787     }
11788
11789     if (currentMove >= forwardMostMove) {
11790       if(gameMode == AnalyzeFile) {
11791           if(appData.loadGameIndex == -1) {
11792             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11793           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11794           } else {
11795           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11796         }
11797       }
11798 //      gameMode = EndOfGame;
11799 //      ModeHighlight();
11800
11801       /* [AS] Clear current move marker at the end of a game */
11802       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11803
11804       return FALSE;
11805     }
11806
11807     toX = moveList[currentMove][2] - AAA;
11808     toY = moveList[currentMove][3] - ONE;
11809
11810     if (moveList[currentMove][1] == '@') {
11811         if (appData.highlightLastMove) {
11812             SetHighlights(-1, -1, toX, toY);
11813         }
11814     } else {
11815         int viaX = moveList[currentMove][5] - AAA;
11816         int viaY = moveList[currentMove][6] - ONE;
11817         fromX = moveList[currentMove][0] - AAA;
11818         fromY = moveList[currentMove][1] - ONE;
11819
11820         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11821
11822         if(moveList[currentMove][4] == ';') { // multi-leg
11823             ChessSquare piece = boards[currentMove][viaY][viaX];
11824             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11825             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11826             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11827             boards[currentMove][viaY][viaX] = piece;
11828         } else
11829         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11830
11831         if (appData.highlightLastMove) {
11832             SetHighlights(fromX, fromY, toX, toY);
11833         }
11834     }
11835     DisplayMove(currentMove);
11836     SendMoveToProgram(currentMove++, &first);
11837     DisplayBothClocks();
11838     DrawPosition(FALSE, boards[currentMove]);
11839     // [HGM] PV info: always display, routine tests if empty
11840     DisplayComment(currentMove - 1, commentList[currentMove]);
11841     return TRUE;
11842 }
11843
11844
11845 int
11846 LoadGameOneMove (ChessMove readAhead)
11847 {
11848     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11849     char promoChar = NULLCHAR;
11850     ChessMove moveType;
11851     char move[MSG_SIZ];
11852     char *p, *q;
11853
11854     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11855         gameMode != AnalyzeMode && gameMode != Training) {
11856         gameFileFP = NULL;
11857         return FALSE;
11858     }
11859
11860     yyboardindex = forwardMostMove;
11861     if (readAhead != EndOfFile) {
11862       moveType = readAhead;
11863     } else {
11864       if (gameFileFP == NULL)
11865           return FALSE;
11866       moveType = (ChessMove) Myylex();
11867     }
11868
11869     done = FALSE;
11870     switch (moveType) {
11871       case Comment:
11872         if (appData.debugMode)
11873           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11874         p = yy_text;
11875
11876         /* append the comment but don't display it */
11877         AppendComment(currentMove, p, FALSE);
11878         return TRUE;
11879
11880       case WhiteCapturesEnPassant:
11881       case BlackCapturesEnPassant:
11882       case WhitePromotion:
11883       case BlackPromotion:
11884       case WhiteNonPromotion:
11885       case BlackNonPromotion:
11886       case NormalMove:
11887       case FirstLeg:
11888       case WhiteKingSideCastle:
11889       case WhiteQueenSideCastle:
11890       case BlackKingSideCastle:
11891       case BlackQueenSideCastle:
11892       case WhiteKingSideCastleWild:
11893       case WhiteQueenSideCastleWild:
11894       case BlackKingSideCastleWild:
11895       case BlackQueenSideCastleWild:
11896       /* PUSH Fabien */
11897       case WhiteHSideCastleFR:
11898       case WhiteASideCastleFR:
11899       case BlackHSideCastleFR:
11900       case BlackASideCastleFR:
11901       /* POP Fabien */
11902         if (appData.debugMode)
11903           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11904         fromX = currentMoveString[0] - AAA;
11905         fromY = currentMoveString[1] - ONE;
11906         toX = currentMoveString[2] - AAA;
11907         toY = currentMoveString[3] - ONE;
11908         promoChar = currentMoveString[4];
11909         if(promoChar == ';') promoChar = NULLCHAR;
11910         break;
11911
11912       case WhiteDrop:
11913       case BlackDrop:
11914         if (appData.debugMode)
11915           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11916         fromX = moveType == WhiteDrop ?
11917           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11918         (int) CharToPiece(ToLower(currentMoveString[0]));
11919         fromY = DROP_RANK;
11920         toX = currentMoveString[2] - AAA;
11921         toY = currentMoveString[3] - ONE;
11922         break;
11923
11924       case WhiteWins:
11925       case BlackWins:
11926       case GameIsDrawn:
11927       case GameUnfinished:
11928         if (appData.debugMode)
11929           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11930         p = strchr(yy_text, '{');
11931         if (p == NULL) p = strchr(yy_text, '(');
11932         if (p == NULL) {
11933             p = yy_text;
11934             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11935         } else {
11936             q = strchr(p, *p == '{' ? '}' : ')');
11937             if (q != NULL) *q = NULLCHAR;
11938             p++;
11939         }
11940         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11941         GameEnds(moveType, p, GE_FILE);
11942         done = TRUE;
11943         if (cmailMsgLoaded) {
11944             ClearHighlights();
11945             flipView = WhiteOnMove(currentMove);
11946             if (moveType == GameUnfinished) flipView = !flipView;
11947             if (appData.debugMode)
11948               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11949         }
11950         break;
11951
11952       case EndOfFile:
11953         if (appData.debugMode)
11954           fprintf(debugFP, "Parser hit end of file\n");
11955         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11956           case MT_NONE:
11957           case MT_CHECK:
11958             break;
11959           case MT_CHECKMATE:
11960           case MT_STAINMATE:
11961             if (WhiteOnMove(currentMove)) {
11962                 GameEnds(BlackWins, "Black mates", GE_FILE);
11963             } else {
11964                 GameEnds(WhiteWins, "White mates", GE_FILE);
11965             }
11966             break;
11967           case MT_STALEMATE:
11968             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11969             break;
11970         }
11971         done = TRUE;
11972         break;
11973
11974       case MoveNumberOne:
11975         if (lastLoadGameStart == GNUChessGame) {
11976             /* GNUChessGames have numbers, but they aren't move numbers */
11977             if (appData.debugMode)
11978               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11979                       yy_text, (int) moveType);
11980             return LoadGameOneMove(EndOfFile); /* tail recursion */
11981         }
11982         /* else fall thru */
11983
11984       case XBoardGame:
11985       case GNUChessGame:
11986       case PGNTag:
11987         /* Reached start of next game in file */
11988         if (appData.debugMode)
11989           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11990         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11991           case MT_NONE:
11992           case MT_CHECK:
11993             break;
11994           case MT_CHECKMATE:
11995           case MT_STAINMATE:
11996             if (WhiteOnMove(currentMove)) {
11997                 GameEnds(BlackWins, "Black mates", GE_FILE);
11998             } else {
11999                 GameEnds(WhiteWins, "White mates", GE_FILE);
12000             }
12001             break;
12002           case MT_STALEMATE:
12003             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12004             break;
12005         }
12006         done = TRUE;
12007         break;
12008
12009       case PositionDiagram:     /* should not happen; ignore */
12010       case ElapsedTime:         /* ignore */
12011       case NAG:                 /* ignore */
12012         if (appData.debugMode)
12013           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12014                   yy_text, (int) moveType);
12015         return LoadGameOneMove(EndOfFile); /* tail recursion */
12016
12017       case IllegalMove:
12018         if (appData.testLegality) {
12019             if (appData.debugMode)
12020               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12021             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12022                     (forwardMostMove / 2) + 1,
12023                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12024             DisplayError(move, 0);
12025             done = TRUE;
12026         } else {
12027             if (appData.debugMode)
12028               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12029                       yy_text, currentMoveString);
12030             fromX = currentMoveString[0] - AAA;
12031             fromY = currentMoveString[1] - ONE;
12032             toX = currentMoveString[2] - AAA;
12033             toY = currentMoveString[3] - ONE;
12034             promoChar = currentMoveString[4];
12035         }
12036         break;
12037
12038       case AmbiguousMove:
12039         if (appData.debugMode)
12040           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12041         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12042                 (forwardMostMove / 2) + 1,
12043                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12044         DisplayError(move, 0);
12045         done = TRUE;
12046         break;
12047
12048       default:
12049       case ImpossibleMove:
12050         if (appData.debugMode)
12051           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12052         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12053                 (forwardMostMove / 2) + 1,
12054                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12055         DisplayError(move, 0);
12056         done = TRUE;
12057         break;
12058     }
12059
12060     if (done) {
12061         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12062             DrawPosition(FALSE, boards[currentMove]);
12063             DisplayBothClocks();
12064             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12065               DisplayComment(currentMove - 1, commentList[currentMove]);
12066         }
12067         (void) StopLoadGameTimer();
12068         gameFileFP = NULL;
12069         cmailOldMove = forwardMostMove;
12070         return FALSE;
12071     } else {
12072         /* currentMoveString is set as a side-effect of yylex */
12073
12074         thinkOutput[0] = NULLCHAR;
12075         MakeMove(fromX, fromY, toX, toY, promoChar);
12076         killX = killY = -1; // [HGM] lion: used up
12077         currentMove = forwardMostMove;
12078         return TRUE;
12079     }
12080 }
12081
12082 /* Load the nth game from the given file */
12083 int
12084 LoadGameFromFile (char *filename, int n, char *title, int useList)
12085 {
12086     FILE *f;
12087     char buf[MSG_SIZ];
12088
12089     if (strcmp(filename, "-") == 0) {
12090         f = stdin;
12091         title = "stdin";
12092     } else {
12093         f = fopen(filename, "rb");
12094         if (f == NULL) {
12095           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12096             DisplayError(buf, errno);
12097             return FALSE;
12098         }
12099     }
12100     if (fseek(f, 0, 0) == -1) {
12101         /* f is not seekable; probably a pipe */
12102         useList = FALSE;
12103     }
12104     if (useList && n == 0) {
12105         int error = GameListBuild(f);
12106         if (error) {
12107             DisplayError(_("Cannot build game list"), error);
12108         } else if (!ListEmpty(&gameList) &&
12109                    ((ListGame *) gameList.tailPred)->number > 1) {
12110             GameListPopUp(f, title);
12111             return TRUE;
12112         }
12113         GameListDestroy();
12114         n = 1;
12115     }
12116     if (n == 0) n = 1;
12117     return LoadGame(f, n, title, FALSE);
12118 }
12119
12120
12121 void
12122 MakeRegisteredMove ()
12123 {
12124     int fromX, fromY, toX, toY;
12125     char promoChar;
12126     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12127         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12128           case CMAIL_MOVE:
12129           case CMAIL_DRAW:
12130             if (appData.debugMode)
12131               fprintf(debugFP, "Restoring %s for game %d\n",
12132                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12133
12134             thinkOutput[0] = NULLCHAR;
12135             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12136             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12137             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12138             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12139             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12140             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12141             MakeMove(fromX, fromY, toX, toY, promoChar);
12142             ShowMove(fromX, fromY, toX, toY);
12143
12144             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12145               case MT_NONE:
12146               case MT_CHECK:
12147                 break;
12148
12149               case MT_CHECKMATE:
12150               case MT_STAINMATE:
12151                 if (WhiteOnMove(currentMove)) {
12152                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12153                 } else {
12154                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12155                 }
12156                 break;
12157
12158               case MT_STALEMATE:
12159                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12160                 break;
12161             }
12162
12163             break;
12164
12165           case CMAIL_RESIGN:
12166             if (WhiteOnMove(currentMove)) {
12167                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12168             } else {
12169                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12170             }
12171             break;
12172
12173           case CMAIL_ACCEPT:
12174             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12175             break;
12176
12177           default:
12178             break;
12179         }
12180     }
12181
12182     return;
12183 }
12184
12185 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12186 int
12187 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12188 {
12189     int retVal;
12190
12191     if (gameNumber > nCmailGames) {
12192         DisplayError(_("No more games in this message"), 0);
12193         return FALSE;
12194     }
12195     if (f == lastLoadGameFP) {
12196         int offset = gameNumber - lastLoadGameNumber;
12197         if (offset == 0) {
12198             cmailMsg[0] = NULLCHAR;
12199             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12200                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12201                 nCmailMovesRegistered--;
12202             }
12203             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12204             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12205                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12206             }
12207         } else {
12208             if (! RegisterMove()) return FALSE;
12209         }
12210     }
12211
12212     retVal = LoadGame(f, gameNumber, title, useList);
12213
12214     /* Make move registered during previous look at this game, if any */
12215     MakeRegisteredMove();
12216
12217     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12218         commentList[currentMove]
12219           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12220         DisplayComment(currentMove - 1, commentList[currentMove]);
12221     }
12222
12223     return retVal;
12224 }
12225
12226 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12227 int
12228 ReloadGame (int offset)
12229 {
12230     int gameNumber = lastLoadGameNumber + offset;
12231     if (lastLoadGameFP == NULL) {
12232         DisplayError(_("No game has been loaded yet"), 0);
12233         return FALSE;
12234     }
12235     if (gameNumber <= 0) {
12236         DisplayError(_("Can't back up any further"), 0);
12237         return FALSE;
12238     }
12239     if (cmailMsgLoaded) {
12240         return CmailLoadGame(lastLoadGameFP, gameNumber,
12241                              lastLoadGameTitle, lastLoadGameUseList);
12242     } else {
12243         return LoadGame(lastLoadGameFP, gameNumber,
12244                         lastLoadGameTitle, lastLoadGameUseList);
12245     }
12246 }
12247
12248 int keys[EmptySquare+1];
12249
12250 int
12251 PositionMatches (Board b1, Board b2)
12252 {
12253     int r, f, sum=0;
12254     switch(appData.searchMode) {
12255         case 1: return CompareWithRights(b1, b2);
12256         case 2:
12257             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12258                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12259             }
12260             return TRUE;
12261         case 3:
12262             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12263               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12264                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12265             }
12266             return sum==0;
12267         case 4:
12268             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12269                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12270             }
12271             return sum==0;
12272     }
12273     return TRUE;
12274 }
12275
12276 #define Q_PROMO  4
12277 #define Q_EP     3
12278 #define Q_BCASTL 2
12279 #define Q_WCASTL 1
12280
12281 int pieceList[256], quickBoard[256];
12282 ChessSquare pieceType[256] = { EmptySquare };
12283 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12284 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12285 int soughtTotal, turn;
12286 Boolean epOK, flipSearch;
12287
12288 typedef struct {
12289     unsigned char piece, to;
12290 } Move;
12291
12292 #define DSIZE (250000)
12293
12294 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12295 Move *moveDatabase = initialSpace;
12296 unsigned int movePtr, dataSize = DSIZE;
12297
12298 int
12299 MakePieceList (Board board, int *counts)
12300 {
12301     int r, f, n=Q_PROMO, total=0;
12302     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12303     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12304         int sq = f + (r<<4);
12305         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12306             quickBoard[sq] = ++n;
12307             pieceList[n] = sq;
12308             pieceType[n] = board[r][f];
12309             counts[board[r][f]]++;
12310             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12311             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12312             total++;
12313         }
12314     }
12315     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12316     return total;
12317 }
12318
12319 void
12320 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12321 {
12322     int sq = fromX + (fromY<<4);
12323     int piece = quickBoard[sq], rook;
12324     quickBoard[sq] = 0;
12325     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12326     if(piece == pieceList[1] && fromY == toY) {
12327       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12328         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12329         moveDatabase[movePtr++].piece = Q_WCASTL;
12330         quickBoard[sq] = piece;
12331         piece = quickBoard[from]; quickBoard[from] = 0;
12332         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12333       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12334         quickBoard[sq] = 0; // remove Rook
12335         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12336         moveDatabase[movePtr++].piece = Q_WCASTL;
12337         quickBoard[sq] = pieceList[1]; // put King
12338         piece = rook;
12339         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12340       }
12341     } else
12342     if(piece == pieceList[2] && fromY == toY) {
12343       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12344         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12345         moveDatabase[movePtr++].piece = Q_BCASTL;
12346         quickBoard[sq] = piece;
12347         piece = quickBoard[from]; quickBoard[from] = 0;
12348         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12349       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12350         quickBoard[sq] = 0; // remove Rook
12351         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12352         moveDatabase[movePtr++].piece = Q_BCASTL;
12353         quickBoard[sq] = pieceList[2]; // put King
12354         piece = rook;
12355         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12356       }
12357     } else
12358     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12359         quickBoard[(fromY<<4)+toX] = 0;
12360         moveDatabase[movePtr].piece = Q_EP;
12361         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12362         moveDatabase[movePtr].to = sq;
12363     } else
12364     if(promoPiece != pieceType[piece]) {
12365         moveDatabase[movePtr++].piece = Q_PROMO;
12366         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12367     }
12368     moveDatabase[movePtr].piece = piece;
12369     quickBoard[sq] = piece;
12370     movePtr++;
12371 }
12372
12373 int
12374 PackGame (Board board)
12375 {
12376     Move *newSpace = NULL;
12377     moveDatabase[movePtr].piece = 0; // terminate previous game
12378     if(movePtr > dataSize) {
12379         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12380         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12381         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12382         if(newSpace) {
12383             int i;
12384             Move *p = moveDatabase, *q = newSpace;
12385             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12386             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12387             moveDatabase = newSpace;
12388         } else { // calloc failed, we must be out of memory. Too bad...
12389             dataSize = 0; // prevent calloc events for all subsequent games
12390             return 0;     // and signal this one isn't cached
12391         }
12392     }
12393     movePtr++;
12394     MakePieceList(board, counts);
12395     return movePtr;
12396 }
12397
12398 int
12399 QuickCompare (Board board, int *minCounts, int *maxCounts)
12400 {   // compare according to search mode
12401     int r, f;
12402     switch(appData.searchMode)
12403     {
12404       case 1: // exact position match
12405         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12406         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12407             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12408         }
12409         break;
12410       case 2: // can have extra material on empty squares
12411         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12412             if(board[r][f] == EmptySquare) continue;
12413             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12414         }
12415         break;
12416       case 3: // material with exact Pawn structure
12417         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12418             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12419             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12420         } // fall through to material comparison
12421       case 4: // exact material
12422         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12423         break;
12424       case 6: // material range with given imbalance
12425         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12426         // fall through to range comparison
12427       case 5: // material range
12428         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12429     }
12430     return TRUE;
12431 }
12432
12433 int
12434 QuickScan (Board board, Move *move)
12435 {   // reconstruct game,and compare all positions in it
12436     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12437     do {
12438         int piece = move->piece;
12439         int to = move->to, from = pieceList[piece];
12440         if(found < 0) { // if already found just scan to game end for final piece count
12441           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12442            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12443            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12444                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12445             ) {
12446             static int lastCounts[EmptySquare+1];
12447             int i;
12448             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12449             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12450           } else stretch = 0;
12451           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12452           if(found >= 0 && !appData.minPieces) return found;
12453         }
12454         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12455           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12456           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12457             piece = (++move)->piece;
12458             from = pieceList[piece];
12459             counts[pieceType[piece]]--;
12460             pieceType[piece] = (ChessSquare) move->to;
12461             counts[move->to]++;
12462           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12463             counts[pieceType[quickBoard[to]]]--;
12464             quickBoard[to] = 0; total--;
12465             move++;
12466             continue;
12467           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12468             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12469             from  = pieceList[piece]; // so this must be King
12470             quickBoard[from] = 0;
12471             pieceList[piece] = to;
12472             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12473             quickBoard[from] = 0; // rook
12474             quickBoard[to] = piece;
12475             to = move->to; piece = move->piece;
12476             goto aftercastle;
12477           }
12478         }
12479         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12480         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12481         quickBoard[from] = 0;
12482       aftercastle:
12483         quickBoard[to] = piece;
12484         pieceList[piece] = to;
12485         cnt++; turn ^= 3;
12486         move++;
12487     } while(1);
12488 }
12489
12490 void
12491 InitSearch ()
12492 {
12493     int r, f;
12494     flipSearch = FALSE;
12495     CopyBoard(soughtBoard, boards[currentMove]);
12496     soughtTotal = MakePieceList(soughtBoard, maxSought);
12497     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12498     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12499     CopyBoard(reverseBoard, boards[currentMove]);
12500     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12501         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12502         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12503         reverseBoard[r][f] = piece;
12504     }
12505     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12506     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12507     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12508                  || (boards[currentMove][CASTLING][2] == NoRights ||
12509                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12510                  && (boards[currentMove][CASTLING][5] == NoRights ||
12511                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12512       ) {
12513         flipSearch = TRUE;
12514         CopyBoard(flipBoard, soughtBoard);
12515         CopyBoard(rotateBoard, reverseBoard);
12516         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12517             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12518             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12519         }
12520     }
12521     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12522     if(appData.searchMode >= 5) {
12523         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12524         MakePieceList(soughtBoard, minSought);
12525         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12526     }
12527     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12528         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12529 }
12530
12531 GameInfo dummyInfo;
12532 static int creatingBook;
12533
12534 int
12535 GameContainsPosition (FILE *f, ListGame *lg)
12536 {
12537     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12538     int fromX, fromY, toX, toY;
12539     char promoChar;
12540     static int initDone=FALSE;
12541
12542     // weed out games based on numerical tag comparison
12543     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12544     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12545     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12546     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12547     if(!initDone) {
12548         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12549         initDone = TRUE;
12550     }
12551     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12552     else CopyBoard(boards[scratch], initialPosition); // default start position
12553     if(lg->moves) {
12554         turn = btm + 1;
12555         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12556         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12557     }
12558     if(btm) plyNr++;
12559     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12560     fseek(f, lg->offset, 0);
12561     yynewfile(f);
12562     while(1) {
12563         yyboardindex = scratch;
12564         quickFlag = plyNr+1;
12565         next = Myylex();
12566         quickFlag = 0;
12567         switch(next) {
12568             case PGNTag:
12569                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12570             default:
12571                 continue;
12572
12573             case XBoardGame:
12574             case GNUChessGame:
12575                 if(plyNr) return -1; // after we have seen moves, this is for new game
12576               continue;
12577
12578             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12579             case ImpossibleMove:
12580             case WhiteWins: // game ends here with these four
12581             case BlackWins:
12582             case GameIsDrawn:
12583             case GameUnfinished:
12584                 return -1;
12585
12586             case IllegalMove:
12587                 if(appData.testLegality) return -1;
12588             case WhiteCapturesEnPassant:
12589             case BlackCapturesEnPassant:
12590             case WhitePromotion:
12591             case BlackPromotion:
12592             case WhiteNonPromotion:
12593             case BlackNonPromotion:
12594             case NormalMove:
12595             case FirstLeg:
12596             case WhiteKingSideCastle:
12597             case WhiteQueenSideCastle:
12598             case BlackKingSideCastle:
12599             case BlackQueenSideCastle:
12600             case WhiteKingSideCastleWild:
12601             case WhiteQueenSideCastleWild:
12602             case BlackKingSideCastleWild:
12603             case BlackQueenSideCastleWild:
12604             case WhiteHSideCastleFR:
12605             case WhiteASideCastleFR:
12606             case BlackHSideCastleFR:
12607             case BlackASideCastleFR:
12608                 fromX = currentMoveString[0] - AAA;
12609                 fromY = currentMoveString[1] - ONE;
12610                 toX = currentMoveString[2] - AAA;
12611                 toY = currentMoveString[3] - ONE;
12612                 promoChar = currentMoveString[4];
12613                 break;
12614             case WhiteDrop:
12615             case BlackDrop:
12616                 fromX = next == WhiteDrop ?
12617                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12618                   (int) CharToPiece(ToLower(currentMoveString[0]));
12619                 fromY = DROP_RANK;
12620                 toX = currentMoveString[2] - AAA;
12621                 toY = currentMoveString[3] - ONE;
12622                 promoChar = 0;
12623                 break;
12624         }
12625         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12626         plyNr++;
12627         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12628         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12629         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12630         if(appData.findMirror) {
12631             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12632             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12633         }
12634     }
12635 }
12636
12637 /* Load the nth game from open file f */
12638 int
12639 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12640 {
12641     ChessMove cm;
12642     char buf[MSG_SIZ];
12643     int gn = gameNumber;
12644     ListGame *lg = NULL;
12645     int numPGNTags = 0;
12646     int err, pos = -1;
12647     GameMode oldGameMode;
12648     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12649
12650     if (appData.debugMode)
12651         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12652
12653     if (gameMode == Training )
12654         SetTrainingModeOff();
12655
12656     oldGameMode = gameMode;
12657     if (gameMode != BeginningOfGame) {
12658       Reset(FALSE, TRUE);
12659     }
12660     killX = killY = -1; // [HGM] lion: in case we did not Reset
12661
12662     gameFileFP = f;
12663     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12664         fclose(lastLoadGameFP);
12665     }
12666
12667     if (useList) {
12668         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12669
12670         if (lg) {
12671             fseek(f, lg->offset, 0);
12672             GameListHighlight(gameNumber);
12673             pos = lg->position;
12674             gn = 1;
12675         }
12676         else {
12677             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12678               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12679             else
12680             DisplayError(_("Game number out of range"), 0);
12681             return FALSE;
12682         }
12683     } else {
12684         GameListDestroy();
12685         if (fseek(f, 0, 0) == -1) {
12686             if (f == lastLoadGameFP ?
12687                 gameNumber == lastLoadGameNumber + 1 :
12688                 gameNumber == 1) {
12689                 gn = 1;
12690             } else {
12691                 DisplayError(_("Can't seek on game file"), 0);
12692                 return FALSE;
12693             }
12694         }
12695     }
12696     lastLoadGameFP = f;
12697     lastLoadGameNumber = gameNumber;
12698     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12699     lastLoadGameUseList = useList;
12700
12701     yynewfile(f);
12702
12703     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12704       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12705                 lg->gameInfo.black);
12706             DisplayTitle(buf);
12707     } else if (*title != NULLCHAR) {
12708         if (gameNumber > 1) {
12709           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12710             DisplayTitle(buf);
12711         } else {
12712             DisplayTitle(title);
12713         }
12714     }
12715
12716     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12717         gameMode = PlayFromGameFile;
12718         ModeHighlight();
12719     }
12720
12721     currentMove = forwardMostMove = backwardMostMove = 0;
12722     CopyBoard(boards[0], initialPosition);
12723     StopClocks();
12724
12725     /*
12726      * Skip the first gn-1 games in the file.
12727      * Also skip over anything that precedes an identifiable
12728      * start of game marker, to avoid being confused by
12729      * garbage at the start of the file.  Currently
12730      * recognized start of game markers are the move number "1",
12731      * the pattern "gnuchess .* game", the pattern
12732      * "^[#;%] [^ ]* game file", and a PGN tag block.
12733      * A game that starts with one of the latter two patterns
12734      * will also have a move number 1, possibly
12735      * following a position diagram.
12736      * 5-4-02: Let's try being more lenient and allowing a game to
12737      * start with an unnumbered move.  Does that break anything?
12738      */
12739     cm = lastLoadGameStart = EndOfFile;
12740     while (gn > 0) {
12741         yyboardindex = forwardMostMove;
12742         cm = (ChessMove) Myylex();
12743         switch (cm) {
12744           case EndOfFile:
12745             if (cmailMsgLoaded) {
12746                 nCmailGames = CMAIL_MAX_GAMES - gn;
12747             } else {
12748                 Reset(TRUE, TRUE);
12749                 DisplayError(_("Game not found in file"), 0);
12750             }
12751             return FALSE;
12752
12753           case GNUChessGame:
12754           case XBoardGame:
12755             gn--;
12756             lastLoadGameStart = cm;
12757             break;
12758
12759           case MoveNumberOne:
12760             switch (lastLoadGameStart) {
12761               case GNUChessGame:
12762               case XBoardGame:
12763               case PGNTag:
12764                 break;
12765               case MoveNumberOne:
12766               case EndOfFile:
12767                 gn--;           /* count this game */
12768                 lastLoadGameStart = cm;
12769                 break;
12770               default:
12771                 /* impossible */
12772                 break;
12773             }
12774             break;
12775
12776           case PGNTag:
12777             switch (lastLoadGameStart) {
12778               case GNUChessGame:
12779               case PGNTag:
12780               case MoveNumberOne:
12781               case EndOfFile:
12782                 gn--;           /* count this game */
12783                 lastLoadGameStart = cm;
12784                 break;
12785               case XBoardGame:
12786                 lastLoadGameStart = cm; /* game counted already */
12787                 break;
12788               default:
12789                 /* impossible */
12790                 break;
12791             }
12792             if (gn > 0) {
12793                 do {
12794                     yyboardindex = forwardMostMove;
12795                     cm = (ChessMove) Myylex();
12796                 } while (cm == PGNTag || cm == Comment);
12797             }
12798             break;
12799
12800           case WhiteWins:
12801           case BlackWins:
12802           case GameIsDrawn:
12803             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12804                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12805                     != CMAIL_OLD_RESULT) {
12806                     nCmailResults ++ ;
12807                     cmailResult[  CMAIL_MAX_GAMES
12808                                 - gn - 1] = CMAIL_OLD_RESULT;
12809                 }
12810             }
12811             break;
12812
12813           case NormalMove:
12814           case FirstLeg:
12815             /* Only a NormalMove can be at the start of a game
12816              * without a position diagram. */
12817             if (lastLoadGameStart == EndOfFile ) {
12818               gn--;
12819               lastLoadGameStart = MoveNumberOne;
12820             }
12821             break;
12822
12823           default:
12824             break;
12825         }
12826     }
12827
12828     if (appData.debugMode)
12829       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12830
12831     if (cm == XBoardGame) {
12832         /* Skip any header junk before position diagram and/or move 1 */
12833         for (;;) {
12834             yyboardindex = forwardMostMove;
12835             cm = (ChessMove) Myylex();
12836
12837             if (cm == EndOfFile ||
12838                 cm == GNUChessGame || cm == XBoardGame) {
12839                 /* Empty game; pretend end-of-file and handle later */
12840                 cm = EndOfFile;
12841                 break;
12842             }
12843
12844             if (cm == MoveNumberOne || cm == PositionDiagram ||
12845                 cm == PGNTag || cm == Comment)
12846               break;
12847         }
12848     } else if (cm == GNUChessGame) {
12849         if (gameInfo.event != NULL) {
12850             free(gameInfo.event);
12851         }
12852         gameInfo.event = StrSave(yy_text);
12853     }
12854
12855     startedFromSetupPosition = FALSE;
12856     while (cm == PGNTag) {
12857         if (appData.debugMode)
12858           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12859         err = ParsePGNTag(yy_text, &gameInfo);
12860         if (!err) numPGNTags++;
12861
12862         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12863         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
12864             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12865             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12866             InitPosition(TRUE);
12867             oldVariant = gameInfo.variant;
12868             if (appData.debugMode)
12869               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12870         }
12871
12872
12873         if (gameInfo.fen != NULL) {
12874           Board initial_position;
12875           startedFromSetupPosition = TRUE;
12876           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12877             Reset(TRUE, TRUE);
12878             DisplayError(_("Bad FEN position in file"), 0);
12879             return FALSE;
12880           }
12881           CopyBoard(boards[0], initial_position);
12882           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
12883             CopyBoard(initialPosition, initial_position);
12884           if (blackPlaysFirst) {
12885             currentMove = forwardMostMove = backwardMostMove = 1;
12886             CopyBoard(boards[1], initial_position);
12887             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12888             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12889             timeRemaining[0][1] = whiteTimeRemaining;
12890             timeRemaining[1][1] = blackTimeRemaining;
12891             if (commentList[0] != NULL) {
12892               commentList[1] = commentList[0];
12893               commentList[0] = NULL;
12894             }
12895           } else {
12896             currentMove = forwardMostMove = backwardMostMove = 0;
12897           }
12898           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12899           {   int i;
12900               initialRulePlies = FENrulePlies;
12901               for( i=0; i< nrCastlingRights; i++ )
12902                   initialRights[i] = initial_position[CASTLING][i];
12903           }
12904           yyboardindex = forwardMostMove;
12905           free(gameInfo.fen);
12906           gameInfo.fen = NULL;
12907         }
12908
12909         yyboardindex = forwardMostMove;
12910         cm = (ChessMove) Myylex();
12911
12912         /* Handle comments interspersed among the tags */
12913         while (cm == Comment) {
12914             char *p;
12915             if (appData.debugMode)
12916               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12917             p = yy_text;
12918             AppendComment(currentMove, p, FALSE);
12919             yyboardindex = forwardMostMove;
12920             cm = (ChessMove) Myylex();
12921         }
12922     }
12923
12924     /* don't rely on existence of Event tag since if game was
12925      * pasted from clipboard the Event tag may not exist
12926      */
12927     if (numPGNTags > 0){
12928         char *tags;
12929         if (gameInfo.variant == VariantNormal) {
12930           VariantClass v = StringToVariant(gameInfo.event);
12931           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12932           if(v < VariantShogi) gameInfo.variant = v;
12933         }
12934         if (!matchMode) {
12935           if( appData.autoDisplayTags ) {
12936             tags = PGNTags(&gameInfo);
12937             TagsPopUp(tags, CmailMsg());
12938             free(tags);
12939           }
12940         }
12941     } else {
12942         /* Make something up, but don't display it now */
12943         SetGameInfo();
12944         TagsPopDown();
12945     }
12946
12947     if (cm == PositionDiagram) {
12948         int i, j;
12949         char *p;
12950         Board initial_position;
12951
12952         if (appData.debugMode)
12953           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12954
12955         if (!startedFromSetupPosition) {
12956             p = yy_text;
12957             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12958               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12959                 switch (*p) {
12960                   case '{':
12961                   case '[':
12962                   case '-':
12963                   case ' ':
12964                   case '\t':
12965                   case '\n':
12966                   case '\r':
12967                     break;
12968                   default:
12969                     initial_position[i][j++] = CharToPiece(*p);
12970                     break;
12971                 }
12972             while (*p == ' ' || *p == '\t' ||
12973                    *p == '\n' || *p == '\r') p++;
12974
12975             if (strncmp(p, "black", strlen("black"))==0)
12976               blackPlaysFirst = TRUE;
12977             else
12978               blackPlaysFirst = FALSE;
12979             startedFromSetupPosition = TRUE;
12980
12981             CopyBoard(boards[0], initial_position);
12982             if (blackPlaysFirst) {
12983                 currentMove = forwardMostMove = backwardMostMove = 1;
12984                 CopyBoard(boards[1], initial_position);
12985                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12986                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12987                 timeRemaining[0][1] = whiteTimeRemaining;
12988                 timeRemaining[1][1] = blackTimeRemaining;
12989                 if (commentList[0] != NULL) {
12990                     commentList[1] = commentList[0];
12991                     commentList[0] = NULL;
12992                 }
12993             } else {
12994                 currentMove = forwardMostMove = backwardMostMove = 0;
12995             }
12996         }
12997         yyboardindex = forwardMostMove;
12998         cm = (ChessMove) Myylex();
12999     }
13000
13001   if(!creatingBook) {
13002     if (first.pr == NoProc) {
13003         StartChessProgram(&first);
13004     }
13005     InitChessProgram(&first, FALSE);
13006     SendToProgram("force\n", &first);
13007     if (startedFromSetupPosition) {
13008         SendBoard(&first, forwardMostMove);
13009     if (appData.debugMode) {
13010         fprintf(debugFP, "Load Game\n");
13011     }
13012         DisplayBothClocks();
13013     }
13014   }
13015
13016     /* [HGM] server: flag to write setup moves in broadcast file as one */
13017     loadFlag = appData.suppressLoadMoves;
13018
13019     while (cm == Comment) {
13020         char *p;
13021         if (appData.debugMode)
13022           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13023         p = yy_text;
13024         AppendComment(currentMove, p, FALSE);
13025         yyboardindex = forwardMostMove;
13026         cm = (ChessMove) Myylex();
13027     }
13028
13029     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13030         cm == WhiteWins || cm == BlackWins ||
13031         cm == GameIsDrawn || cm == GameUnfinished) {
13032         DisplayMessage("", _("No moves in game"));
13033         if (cmailMsgLoaded) {
13034             if (appData.debugMode)
13035               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13036             ClearHighlights();
13037             flipView = FALSE;
13038         }
13039         DrawPosition(FALSE, boards[currentMove]);
13040         DisplayBothClocks();
13041         gameMode = EditGame;
13042         ModeHighlight();
13043         gameFileFP = NULL;
13044         cmailOldMove = 0;
13045         return TRUE;
13046     }
13047
13048     // [HGM] PV info: routine tests if comment empty
13049     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13050         DisplayComment(currentMove - 1, commentList[currentMove]);
13051     }
13052     if (!matchMode && appData.timeDelay != 0)
13053       DrawPosition(FALSE, boards[currentMove]);
13054
13055     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13056       programStats.ok_to_send = 1;
13057     }
13058
13059     /* if the first token after the PGN tags is a move
13060      * and not move number 1, retrieve it from the parser
13061      */
13062     if (cm != MoveNumberOne)
13063         LoadGameOneMove(cm);
13064
13065     /* load the remaining moves from the file */
13066     while (LoadGameOneMove(EndOfFile)) {
13067       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13068       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13069     }
13070
13071     /* rewind to the start of the game */
13072     currentMove = backwardMostMove;
13073
13074     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13075
13076     if (oldGameMode == AnalyzeFile) {
13077       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13078       AnalyzeFileEvent();
13079     } else
13080     if (oldGameMode == AnalyzeMode) {
13081       AnalyzeFileEvent();
13082     }
13083
13084     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13085         long int w, b; // [HGM] adjourn: restore saved clock times
13086         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13087         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13088             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13089             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13090         }
13091     }
13092
13093     if(creatingBook) return TRUE;
13094     if (!matchMode && pos > 0) {
13095         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13096     } else
13097     if (matchMode || appData.timeDelay == 0) {
13098       ToEndEvent();
13099     } else if (appData.timeDelay > 0) {
13100       AutoPlayGameLoop();
13101     }
13102
13103     if (appData.debugMode)
13104         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13105
13106     loadFlag = 0; /* [HGM] true game starts */
13107     return TRUE;
13108 }
13109
13110 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13111 int
13112 ReloadPosition (int offset)
13113 {
13114     int positionNumber = lastLoadPositionNumber + offset;
13115     if (lastLoadPositionFP == NULL) {
13116         DisplayError(_("No position has been loaded yet"), 0);
13117         return FALSE;
13118     }
13119     if (positionNumber <= 0) {
13120         DisplayError(_("Can't back up any further"), 0);
13121         return FALSE;
13122     }
13123     return LoadPosition(lastLoadPositionFP, positionNumber,
13124                         lastLoadPositionTitle);
13125 }
13126
13127 /* Load the nth position from the given file */
13128 int
13129 LoadPositionFromFile (char *filename, int n, char *title)
13130 {
13131     FILE *f;
13132     char buf[MSG_SIZ];
13133
13134     if (strcmp(filename, "-") == 0) {
13135         return LoadPosition(stdin, n, "stdin");
13136     } else {
13137         f = fopen(filename, "rb");
13138         if (f == NULL) {
13139             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13140             DisplayError(buf, errno);
13141             return FALSE;
13142         } else {
13143             return LoadPosition(f, n, title);
13144         }
13145     }
13146 }
13147
13148 /* Load the nth position from the given open file, and close it */
13149 int
13150 LoadPosition (FILE *f, int positionNumber, char *title)
13151 {
13152     char *p, line[MSG_SIZ];
13153     Board initial_position;
13154     int i, j, fenMode, pn;
13155
13156     if (gameMode == Training )
13157         SetTrainingModeOff();
13158
13159     if (gameMode != BeginningOfGame) {
13160         Reset(FALSE, TRUE);
13161     }
13162     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13163         fclose(lastLoadPositionFP);
13164     }
13165     if (positionNumber == 0) positionNumber = 1;
13166     lastLoadPositionFP = f;
13167     lastLoadPositionNumber = positionNumber;
13168     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13169     if (first.pr == NoProc && !appData.noChessProgram) {
13170       StartChessProgram(&first);
13171       InitChessProgram(&first, FALSE);
13172     }
13173     pn = positionNumber;
13174     if (positionNumber < 0) {
13175         /* Negative position number means to seek to that byte offset */
13176         if (fseek(f, -positionNumber, 0) == -1) {
13177             DisplayError(_("Can't seek on position file"), 0);
13178             return FALSE;
13179         };
13180         pn = 1;
13181     } else {
13182         if (fseek(f, 0, 0) == -1) {
13183             if (f == lastLoadPositionFP ?
13184                 positionNumber == lastLoadPositionNumber + 1 :
13185                 positionNumber == 1) {
13186                 pn = 1;
13187             } else {
13188                 DisplayError(_("Can't seek on position file"), 0);
13189                 return FALSE;
13190             }
13191         }
13192     }
13193     /* See if this file is FEN or old-style xboard */
13194     if (fgets(line, MSG_SIZ, f) == NULL) {
13195         DisplayError(_("Position not found in file"), 0);
13196         return FALSE;
13197     }
13198     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13199     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13200
13201     if (pn >= 2) {
13202         if (fenMode || line[0] == '#') pn--;
13203         while (pn > 0) {
13204             /* skip positions before number pn */
13205             if (fgets(line, MSG_SIZ, f) == NULL) {
13206                 Reset(TRUE, TRUE);
13207                 DisplayError(_("Position not found in file"), 0);
13208                 return FALSE;
13209             }
13210             if (fenMode || line[0] == '#') pn--;
13211         }
13212     }
13213
13214     if (fenMode) {
13215         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13216             DisplayError(_("Bad FEN position in file"), 0);
13217             return FALSE;
13218         }
13219     } else {
13220         (void) fgets(line, MSG_SIZ, f);
13221         (void) fgets(line, MSG_SIZ, f);
13222
13223         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13224             (void) fgets(line, MSG_SIZ, f);
13225             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13226                 if (*p == ' ')
13227                   continue;
13228                 initial_position[i][j++] = CharToPiece(*p);
13229             }
13230         }
13231
13232         blackPlaysFirst = FALSE;
13233         if (!feof(f)) {
13234             (void) fgets(line, MSG_SIZ, f);
13235             if (strncmp(line, "black", strlen("black"))==0)
13236               blackPlaysFirst = TRUE;
13237         }
13238     }
13239     startedFromSetupPosition = TRUE;
13240
13241     CopyBoard(boards[0], initial_position);
13242     if (blackPlaysFirst) {
13243         currentMove = forwardMostMove = backwardMostMove = 1;
13244         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13245         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13246         CopyBoard(boards[1], initial_position);
13247         DisplayMessage("", _("Black to play"));
13248     } else {
13249         currentMove = forwardMostMove = backwardMostMove = 0;
13250         DisplayMessage("", _("White to play"));
13251     }
13252     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13253     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13254         SendToProgram("force\n", &first);
13255         SendBoard(&first, forwardMostMove);
13256     }
13257     if (appData.debugMode) {
13258 int i, j;
13259   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13260   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13261         fprintf(debugFP, "Load Position\n");
13262     }
13263
13264     if (positionNumber > 1) {
13265       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13266         DisplayTitle(line);
13267     } else {
13268         DisplayTitle(title);
13269     }
13270     gameMode = EditGame;
13271     ModeHighlight();
13272     ResetClocks();
13273     timeRemaining[0][1] = whiteTimeRemaining;
13274     timeRemaining[1][1] = blackTimeRemaining;
13275     DrawPosition(FALSE, boards[currentMove]);
13276
13277     return TRUE;
13278 }
13279
13280
13281 void
13282 CopyPlayerNameIntoFileName (char **dest, char *src)
13283 {
13284     while (*src != NULLCHAR && *src != ',') {
13285         if (*src == ' ') {
13286             *(*dest)++ = '_';
13287             src++;
13288         } else {
13289             *(*dest)++ = *src++;
13290         }
13291     }
13292 }
13293
13294 char *
13295 DefaultFileName (char *ext)
13296 {
13297     static char def[MSG_SIZ];
13298     char *p;
13299
13300     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13301         p = def;
13302         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13303         *p++ = '-';
13304         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13305         *p++ = '.';
13306         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13307     } else {
13308         def[0] = NULLCHAR;
13309     }
13310     return def;
13311 }
13312
13313 /* Save the current game to the given file */
13314 int
13315 SaveGameToFile (char *filename, int append)
13316 {
13317     FILE *f;
13318     char buf[MSG_SIZ];
13319     int result, i, t,tot=0;
13320
13321     if (strcmp(filename, "-") == 0) {
13322         return SaveGame(stdout, 0, NULL);
13323     } else {
13324         for(i=0; i<10; i++) { // upto 10 tries
13325              f = fopen(filename, append ? "a" : "w");
13326              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13327              if(f || errno != 13) break;
13328              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13329              tot += t;
13330         }
13331         if (f == NULL) {
13332             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13333             DisplayError(buf, errno);
13334             return FALSE;
13335         } else {
13336             safeStrCpy(buf, lastMsg, MSG_SIZ);
13337             DisplayMessage(_("Waiting for access to save file"), "");
13338             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13339             DisplayMessage(_("Saving game"), "");
13340             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13341             result = SaveGame(f, 0, NULL);
13342             DisplayMessage(buf, "");
13343             return result;
13344         }
13345     }
13346 }
13347
13348 char *
13349 SavePart (char *str)
13350 {
13351     static char buf[MSG_SIZ];
13352     char *p;
13353
13354     p = strchr(str, ' ');
13355     if (p == NULL) return str;
13356     strncpy(buf, str, p - str);
13357     buf[p - str] = NULLCHAR;
13358     return buf;
13359 }
13360
13361 #define PGN_MAX_LINE 75
13362
13363 #define PGN_SIDE_WHITE  0
13364 #define PGN_SIDE_BLACK  1
13365
13366 static int
13367 FindFirstMoveOutOfBook (int side)
13368 {
13369     int result = -1;
13370
13371     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13372         int index = backwardMostMove;
13373         int has_book_hit = 0;
13374
13375         if( (index % 2) != side ) {
13376             index++;
13377         }
13378
13379         while( index < forwardMostMove ) {
13380             /* Check to see if engine is in book */
13381             int depth = pvInfoList[index].depth;
13382             int score = pvInfoList[index].score;
13383             int in_book = 0;
13384
13385             if( depth <= 2 ) {
13386                 in_book = 1;
13387             }
13388             else if( score == 0 && depth == 63 ) {
13389                 in_book = 1; /* Zappa */
13390             }
13391             else if( score == 2 && depth == 99 ) {
13392                 in_book = 1; /* Abrok */
13393             }
13394
13395             has_book_hit += in_book;
13396
13397             if( ! in_book ) {
13398                 result = index;
13399
13400                 break;
13401             }
13402
13403             index += 2;
13404         }
13405     }
13406
13407     return result;
13408 }
13409
13410 void
13411 GetOutOfBookInfo (char * buf)
13412 {
13413     int oob[2];
13414     int i;
13415     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13416
13417     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13418     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13419
13420     *buf = '\0';
13421
13422     if( oob[0] >= 0 || oob[1] >= 0 ) {
13423         for( i=0; i<2; i++ ) {
13424             int idx = oob[i];
13425
13426             if( idx >= 0 ) {
13427                 if( i > 0 && oob[0] >= 0 ) {
13428                     strcat( buf, "   " );
13429                 }
13430
13431                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13432                 sprintf( buf+strlen(buf), "%s%.2f",
13433                     pvInfoList[idx].score >= 0 ? "+" : "",
13434                     pvInfoList[idx].score / 100.0 );
13435             }
13436         }
13437     }
13438 }
13439
13440 /* Save game in PGN style */
13441 static void
13442 SaveGamePGN2 (FILE *f)
13443 {
13444     int i, offset, linelen, newblock;
13445 //    char *movetext;
13446     char numtext[32];
13447     int movelen, numlen, blank;
13448     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13449
13450     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13451
13452     PrintPGNTags(f, &gameInfo);
13453
13454     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13455
13456     if (backwardMostMove > 0 || startedFromSetupPosition) {
13457         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13458         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13459         fprintf(f, "\n{--------------\n");
13460         PrintPosition(f, backwardMostMove);
13461         fprintf(f, "--------------}\n");
13462         free(fen);
13463     }
13464     else {
13465         /* [AS] Out of book annotation */
13466         if( appData.saveOutOfBookInfo ) {
13467             char buf[64];
13468
13469             GetOutOfBookInfo( buf );
13470
13471             if( buf[0] != '\0' ) {
13472                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13473             }
13474         }
13475
13476         fprintf(f, "\n");
13477     }
13478
13479     i = backwardMostMove;
13480     linelen = 0;
13481     newblock = TRUE;
13482
13483     while (i < forwardMostMove) {
13484         /* Print comments preceding this move */
13485         if (commentList[i] != NULL) {
13486             if (linelen > 0) fprintf(f, "\n");
13487             fprintf(f, "%s", commentList[i]);
13488             linelen = 0;
13489             newblock = TRUE;
13490         }
13491
13492         /* Format move number */
13493         if ((i % 2) == 0)
13494           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13495         else
13496           if (newblock)
13497             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13498           else
13499             numtext[0] = NULLCHAR;
13500
13501         numlen = strlen(numtext);
13502         newblock = FALSE;
13503
13504         /* Print move number */
13505         blank = linelen > 0 && numlen > 0;
13506         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13507             fprintf(f, "\n");
13508             linelen = 0;
13509             blank = 0;
13510         }
13511         if (blank) {
13512             fprintf(f, " ");
13513             linelen++;
13514         }
13515         fprintf(f, "%s", numtext);
13516         linelen += numlen;
13517
13518         /* Get move */
13519         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13520         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13521
13522         /* Print move */
13523         blank = linelen > 0 && movelen > 0;
13524         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13525             fprintf(f, "\n");
13526             linelen = 0;
13527             blank = 0;
13528         }
13529         if (blank) {
13530             fprintf(f, " ");
13531             linelen++;
13532         }
13533         fprintf(f, "%s", move_buffer);
13534         linelen += movelen;
13535
13536         /* [AS] Add PV info if present */
13537         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13538             /* [HGM] add time */
13539             char buf[MSG_SIZ]; int seconds;
13540
13541             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13542
13543             if( seconds <= 0)
13544               buf[0] = 0;
13545             else
13546               if( seconds < 30 )
13547                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13548               else
13549                 {
13550                   seconds = (seconds + 4)/10; // round to full seconds
13551                   if( seconds < 60 )
13552                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13553                   else
13554                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13555                 }
13556
13557             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13558                       pvInfoList[i].score >= 0 ? "+" : "",
13559                       pvInfoList[i].score / 100.0,
13560                       pvInfoList[i].depth,
13561                       buf );
13562
13563             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13564
13565             /* Print score/depth */
13566             blank = linelen > 0 && movelen > 0;
13567             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13568                 fprintf(f, "\n");
13569                 linelen = 0;
13570                 blank = 0;
13571             }
13572             if (blank) {
13573                 fprintf(f, " ");
13574                 linelen++;
13575             }
13576             fprintf(f, "%s", move_buffer);
13577             linelen += movelen;
13578         }
13579
13580         i++;
13581     }
13582
13583     /* Start a new line */
13584     if (linelen > 0) fprintf(f, "\n");
13585
13586     /* Print comments after last move */
13587     if (commentList[i] != NULL) {
13588         fprintf(f, "%s\n", commentList[i]);
13589     }
13590
13591     /* Print result */
13592     if (gameInfo.resultDetails != NULL &&
13593         gameInfo.resultDetails[0] != NULLCHAR) {
13594         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13595         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13596            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13597             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13598         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13599     } else {
13600         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13601     }
13602 }
13603
13604 /* Save game in PGN style and close the file */
13605 int
13606 SaveGamePGN (FILE *f)
13607 {
13608     SaveGamePGN2(f);
13609     fclose(f);
13610     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13611     return TRUE;
13612 }
13613
13614 /* Save game in old style and close the file */
13615 int
13616 SaveGameOldStyle (FILE *f)
13617 {
13618     int i, offset;
13619     time_t tm;
13620
13621     tm = time((time_t *) NULL);
13622
13623     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13624     PrintOpponents(f);
13625
13626     if (backwardMostMove > 0 || startedFromSetupPosition) {
13627         fprintf(f, "\n[--------------\n");
13628         PrintPosition(f, backwardMostMove);
13629         fprintf(f, "--------------]\n");
13630     } else {
13631         fprintf(f, "\n");
13632     }
13633
13634     i = backwardMostMove;
13635     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13636
13637     while (i < forwardMostMove) {
13638         if (commentList[i] != NULL) {
13639             fprintf(f, "[%s]\n", commentList[i]);
13640         }
13641
13642         if ((i % 2) == 1) {
13643             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13644             i++;
13645         } else {
13646             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13647             i++;
13648             if (commentList[i] != NULL) {
13649                 fprintf(f, "\n");
13650                 continue;
13651             }
13652             if (i >= forwardMostMove) {
13653                 fprintf(f, "\n");
13654                 break;
13655             }
13656             fprintf(f, "%s\n", parseList[i]);
13657             i++;
13658         }
13659     }
13660
13661     if (commentList[i] != NULL) {
13662         fprintf(f, "[%s]\n", commentList[i]);
13663     }
13664
13665     /* This isn't really the old style, but it's close enough */
13666     if (gameInfo.resultDetails != NULL &&
13667         gameInfo.resultDetails[0] != NULLCHAR) {
13668         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13669                 gameInfo.resultDetails);
13670     } else {
13671         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13672     }
13673
13674     fclose(f);
13675     return TRUE;
13676 }
13677
13678 /* Save the current game to open file f and close the file */
13679 int
13680 SaveGame (FILE *f, int dummy, char *dummy2)
13681 {
13682     if (gameMode == EditPosition) EditPositionDone(TRUE);
13683     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13684     if (appData.oldSaveStyle)
13685       return SaveGameOldStyle(f);
13686     else
13687       return SaveGamePGN(f);
13688 }
13689
13690 /* Save the current position to the given file */
13691 int
13692 SavePositionToFile (char *filename)
13693 {
13694     FILE *f;
13695     char buf[MSG_SIZ];
13696
13697     if (strcmp(filename, "-") == 0) {
13698         return SavePosition(stdout, 0, NULL);
13699     } else {
13700         f = fopen(filename, "a");
13701         if (f == NULL) {
13702             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13703             DisplayError(buf, errno);
13704             return FALSE;
13705         } else {
13706             safeStrCpy(buf, lastMsg, MSG_SIZ);
13707             DisplayMessage(_("Waiting for access to save file"), "");
13708             flock(fileno(f), LOCK_EX); // [HGM] lock
13709             DisplayMessage(_("Saving position"), "");
13710             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13711             SavePosition(f, 0, NULL);
13712             DisplayMessage(buf, "");
13713             return TRUE;
13714         }
13715     }
13716 }
13717
13718 /* Save the current position to the given open file and close the file */
13719 int
13720 SavePosition (FILE *f, int dummy, char *dummy2)
13721 {
13722     time_t tm;
13723     char *fen;
13724
13725     if (gameMode == EditPosition) EditPositionDone(TRUE);
13726     if (appData.oldSaveStyle) {
13727         tm = time((time_t *) NULL);
13728
13729         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13730         PrintOpponents(f);
13731         fprintf(f, "[--------------\n");
13732         PrintPosition(f, currentMove);
13733         fprintf(f, "--------------]\n");
13734     } else {
13735         fen = PositionToFEN(currentMove, NULL, 1);
13736         fprintf(f, "%s\n", fen);
13737         free(fen);
13738     }
13739     fclose(f);
13740     return TRUE;
13741 }
13742
13743 void
13744 ReloadCmailMsgEvent (int unregister)
13745 {
13746 #if !WIN32
13747     static char *inFilename = NULL;
13748     static char *outFilename;
13749     int i;
13750     struct stat inbuf, outbuf;
13751     int status;
13752
13753     /* Any registered moves are unregistered if unregister is set, */
13754     /* i.e. invoked by the signal handler */
13755     if (unregister) {
13756         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13757             cmailMoveRegistered[i] = FALSE;
13758             if (cmailCommentList[i] != NULL) {
13759                 free(cmailCommentList[i]);
13760                 cmailCommentList[i] = NULL;
13761             }
13762         }
13763         nCmailMovesRegistered = 0;
13764     }
13765
13766     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13767         cmailResult[i] = CMAIL_NOT_RESULT;
13768     }
13769     nCmailResults = 0;
13770
13771     if (inFilename == NULL) {
13772         /* Because the filenames are static they only get malloced once  */
13773         /* and they never get freed                                      */
13774         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13775         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13776
13777         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13778         sprintf(outFilename, "%s.out", appData.cmailGameName);
13779     }
13780
13781     status = stat(outFilename, &outbuf);
13782     if (status < 0) {
13783         cmailMailedMove = FALSE;
13784     } else {
13785         status = stat(inFilename, &inbuf);
13786         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13787     }
13788
13789     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13790        counts the games, notes how each one terminated, etc.
13791
13792        It would be nice to remove this kludge and instead gather all
13793        the information while building the game list.  (And to keep it
13794        in the game list nodes instead of having a bunch of fixed-size
13795        parallel arrays.)  Note this will require getting each game's
13796        termination from the PGN tags, as the game list builder does
13797        not process the game moves.  --mann
13798        */
13799     cmailMsgLoaded = TRUE;
13800     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13801
13802     /* Load first game in the file or popup game menu */
13803     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13804
13805 #endif /* !WIN32 */
13806     return;
13807 }
13808
13809 int
13810 RegisterMove ()
13811 {
13812     FILE *f;
13813     char string[MSG_SIZ];
13814
13815     if (   cmailMailedMove
13816         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13817         return TRUE;            /* Allow free viewing  */
13818     }
13819
13820     /* Unregister move to ensure that we don't leave RegisterMove        */
13821     /* with the move registered when the conditions for registering no   */
13822     /* longer hold                                                       */
13823     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13824         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13825         nCmailMovesRegistered --;
13826
13827         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13828           {
13829               free(cmailCommentList[lastLoadGameNumber - 1]);
13830               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13831           }
13832     }
13833
13834     if (cmailOldMove == -1) {
13835         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13836         return FALSE;
13837     }
13838
13839     if (currentMove > cmailOldMove + 1) {
13840         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13841         return FALSE;
13842     }
13843
13844     if (currentMove < cmailOldMove) {
13845         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13846         return FALSE;
13847     }
13848
13849     if (forwardMostMove > currentMove) {
13850         /* Silently truncate extra moves */
13851         TruncateGame();
13852     }
13853
13854     if (   (currentMove == cmailOldMove + 1)
13855         || (   (currentMove == cmailOldMove)
13856             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13857                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13858         if (gameInfo.result != GameUnfinished) {
13859             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13860         }
13861
13862         if (commentList[currentMove] != NULL) {
13863             cmailCommentList[lastLoadGameNumber - 1]
13864               = StrSave(commentList[currentMove]);
13865         }
13866         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13867
13868         if (appData.debugMode)
13869           fprintf(debugFP, "Saving %s for game %d\n",
13870                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13871
13872         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13873
13874         f = fopen(string, "w");
13875         if (appData.oldSaveStyle) {
13876             SaveGameOldStyle(f); /* also closes the file */
13877
13878             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13879             f = fopen(string, "w");
13880             SavePosition(f, 0, NULL); /* also closes the file */
13881         } else {
13882             fprintf(f, "{--------------\n");
13883             PrintPosition(f, currentMove);
13884             fprintf(f, "--------------}\n\n");
13885
13886             SaveGame(f, 0, NULL); /* also closes the file*/
13887         }
13888
13889         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13890         nCmailMovesRegistered ++;
13891     } else if (nCmailGames == 1) {
13892         DisplayError(_("You have not made a move yet"), 0);
13893         return FALSE;
13894     }
13895
13896     return TRUE;
13897 }
13898
13899 void
13900 MailMoveEvent ()
13901 {
13902 #if !WIN32
13903     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13904     FILE *commandOutput;
13905     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13906     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13907     int nBuffers;
13908     int i;
13909     int archived;
13910     char *arcDir;
13911
13912     if (! cmailMsgLoaded) {
13913         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13914         return;
13915     }
13916
13917     if (nCmailGames == nCmailResults) {
13918         DisplayError(_("No unfinished games"), 0);
13919         return;
13920     }
13921
13922 #if CMAIL_PROHIBIT_REMAIL
13923     if (cmailMailedMove) {
13924       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);
13925         DisplayError(msg, 0);
13926         return;
13927     }
13928 #endif
13929
13930     if (! (cmailMailedMove || RegisterMove())) return;
13931
13932     if (   cmailMailedMove
13933         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13934       snprintf(string, MSG_SIZ, partCommandString,
13935                appData.debugMode ? " -v" : "", appData.cmailGameName);
13936         commandOutput = popen(string, "r");
13937
13938         if (commandOutput == NULL) {
13939             DisplayError(_("Failed to invoke cmail"), 0);
13940         } else {
13941             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13942                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13943             }
13944             if (nBuffers > 1) {
13945                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13946                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13947                 nBytes = MSG_SIZ - 1;
13948             } else {
13949                 (void) memcpy(msg, buffer, nBytes);
13950             }
13951             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13952
13953             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13954                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13955
13956                 archived = TRUE;
13957                 for (i = 0; i < nCmailGames; i ++) {
13958                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13959                         archived = FALSE;
13960                     }
13961                 }
13962                 if (   archived
13963                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13964                         != NULL)) {
13965                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13966                            arcDir,
13967                            appData.cmailGameName,
13968                            gameInfo.date);
13969                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13970                     cmailMsgLoaded = FALSE;
13971                 }
13972             }
13973
13974             DisplayInformation(msg);
13975             pclose(commandOutput);
13976         }
13977     } else {
13978         if ((*cmailMsg) != '\0') {
13979             DisplayInformation(cmailMsg);
13980         }
13981     }
13982
13983     return;
13984 #endif /* !WIN32 */
13985 }
13986
13987 char *
13988 CmailMsg ()
13989 {
13990 #if WIN32
13991     return NULL;
13992 #else
13993     int  prependComma = 0;
13994     char number[5];
13995     char string[MSG_SIZ];       /* Space for game-list */
13996     int  i;
13997
13998     if (!cmailMsgLoaded) return "";
13999
14000     if (cmailMailedMove) {
14001       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14002     } else {
14003         /* Create a list of games left */
14004       snprintf(string, MSG_SIZ, "[");
14005         for (i = 0; i < nCmailGames; i ++) {
14006             if (! (   cmailMoveRegistered[i]
14007                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14008                 if (prependComma) {
14009                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14010                 } else {
14011                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14012                     prependComma = 1;
14013                 }
14014
14015                 strcat(string, number);
14016             }
14017         }
14018         strcat(string, "]");
14019
14020         if (nCmailMovesRegistered + nCmailResults == 0) {
14021             switch (nCmailGames) {
14022               case 1:
14023                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14024                 break;
14025
14026               case 2:
14027                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14028                 break;
14029
14030               default:
14031                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14032                          nCmailGames);
14033                 break;
14034             }
14035         } else {
14036             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14037               case 1:
14038                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14039                          string);
14040                 break;
14041
14042               case 0:
14043                 if (nCmailResults == nCmailGames) {
14044                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14045                 } else {
14046                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14047                 }
14048                 break;
14049
14050               default:
14051                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14052                          string);
14053             }
14054         }
14055     }
14056     return cmailMsg;
14057 #endif /* WIN32 */
14058 }
14059
14060 void
14061 ResetGameEvent ()
14062 {
14063     if (gameMode == Training)
14064       SetTrainingModeOff();
14065
14066     Reset(TRUE, TRUE);
14067     cmailMsgLoaded = FALSE;
14068     if (appData.icsActive) {
14069       SendToICS(ics_prefix);
14070       SendToICS("refresh\n");
14071     }
14072 }
14073
14074 void
14075 ExitEvent (int status)
14076 {
14077     exiting++;
14078     if (exiting > 2) {
14079       /* Give up on clean exit */
14080       exit(status);
14081     }
14082     if (exiting > 1) {
14083       /* Keep trying for clean exit */
14084       return;
14085     }
14086
14087     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14088     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14089
14090     if (telnetISR != NULL) {
14091       RemoveInputSource(telnetISR);
14092     }
14093     if (icsPR != NoProc) {
14094       DestroyChildProcess(icsPR, TRUE);
14095     }
14096
14097     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14098     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14099
14100     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14101     /* make sure this other one finishes before killing it!                  */
14102     if(endingGame) { int count = 0;
14103         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14104         while(endingGame && count++ < 10) DoSleep(1);
14105         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14106     }
14107
14108     /* Kill off chess programs */
14109     if (first.pr != NoProc) {
14110         ExitAnalyzeMode();
14111
14112         DoSleep( appData.delayBeforeQuit );
14113         SendToProgram("quit\n", &first);
14114         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14115     }
14116     if (second.pr != NoProc) {
14117         DoSleep( appData.delayBeforeQuit );
14118         SendToProgram("quit\n", &second);
14119         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14120     }
14121     if (first.isr != NULL) {
14122         RemoveInputSource(first.isr);
14123     }
14124     if (second.isr != NULL) {
14125         RemoveInputSource(second.isr);
14126     }
14127
14128     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14129     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14130
14131     ShutDownFrontEnd();
14132     exit(status);
14133 }
14134
14135 void
14136 PauseEngine (ChessProgramState *cps)
14137 {
14138     SendToProgram("pause\n", cps);
14139     cps->pause = 2;
14140 }
14141
14142 void
14143 UnPauseEngine (ChessProgramState *cps)
14144 {
14145     SendToProgram("resume\n", cps);
14146     cps->pause = 1;
14147 }
14148
14149 void
14150 PauseEvent ()
14151 {
14152     if (appData.debugMode)
14153         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14154     if (pausing) {
14155         pausing = FALSE;
14156         ModeHighlight();
14157         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14158             StartClocks();
14159             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14160                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14161                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14162             }
14163             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14164             HandleMachineMove(stashedInputMove, stalledEngine);
14165             stalledEngine = NULL;
14166             return;
14167         }
14168         if (gameMode == MachinePlaysWhite ||
14169             gameMode == TwoMachinesPlay   ||
14170             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14171             if(first.pause)  UnPauseEngine(&first);
14172             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14173             if(second.pause) UnPauseEngine(&second);
14174             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14175             StartClocks();
14176         } else {
14177             DisplayBothClocks();
14178         }
14179         if (gameMode == PlayFromGameFile) {
14180             if (appData.timeDelay >= 0)
14181                 AutoPlayGameLoop();
14182         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14183             Reset(FALSE, TRUE);
14184             SendToICS(ics_prefix);
14185             SendToICS("refresh\n");
14186         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14187             ForwardInner(forwardMostMove);
14188         }
14189         pauseExamInvalid = FALSE;
14190     } else {
14191         switch (gameMode) {
14192           default:
14193             return;
14194           case IcsExamining:
14195             pauseExamForwardMostMove = forwardMostMove;
14196             pauseExamInvalid = FALSE;
14197             /* fall through */
14198           case IcsObserving:
14199           case IcsPlayingWhite:
14200           case IcsPlayingBlack:
14201             pausing = TRUE;
14202             ModeHighlight();
14203             return;
14204           case PlayFromGameFile:
14205             (void) StopLoadGameTimer();
14206             pausing = TRUE;
14207             ModeHighlight();
14208             break;
14209           case BeginningOfGame:
14210             if (appData.icsActive) return;
14211             /* else fall through */
14212           case MachinePlaysWhite:
14213           case MachinePlaysBlack:
14214           case TwoMachinesPlay:
14215             if (forwardMostMove == 0)
14216               return;           /* don't pause if no one has moved */
14217             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14218                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14219                 if(onMove->pause) {           // thinking engine can be paused
14220                     PauseEngine(onMove);      // do it
14221                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14222                         PauseEngine(onMove->other);
14223                     else
14224                         SendToProgram("easy\n", onMove->other);
14225                     StopClocks();
14226                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14227             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14228                 if(first.pause) {
14229                     PauseEngine(&first);
14230                     StopClocks();
14231                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14232             } else { // human on move, pause pondering by either method
14233                 if(first.pause)
14234                     PauseEngine(&first);
14235                 else if(appData.ponderNextMove)
14236                     SendToProgram("easy\n", &first);
14237                 StopClocks();
14238             }
14239             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14240           case AnalyzeMode:
14241             pausing = TRUE;
14242             ModeHighlight();
14243             break;
14244         }
14245     }
14246 }
14247
14248 void
14249 EditCommentEvent ()
14250 {
14251     char title[MSG_SIZ];
14252
14253     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14254       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14255     } else {
14256       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14257                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14258                parseList[currentMove - 1]);
14259     }
14260
14261     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14262 }
14263
14264
14265 void
14266 EditTagsEvent ()
14267 {
14268     char *tags = PGNTags(&gameInfo);
14269     bookUp = FALSE;
14270     EditTagsPopUp(tags, NULL);
14271     free(tags);
14272 }
14273
14274 void
14275 ToggleSecond ()
14276 {
14277   if(second.analyzing) {
14278     SendToProgram("exit\n", &second);
14279     second.analyzing = FALSE;
14280   } else {
14281     if (second.pr == NoProc) StartChessProgram(&second);
14282     InitChessProgram(&second, FALSE);
14283     FeedMovesToProgram(&second, currentMove);
14284
14285     SendToProgram("analyze\n", &second);
14286     second.analyzing = TRUE;
14287   }
14288 }
14289
14290 /* Toggle ShowThinking */
14291 void
14292 ToggleShowThinking()
14293 {
14294   appData.showThinking = !appData.showThinking;
14295   ShowThinkingEvent();
14296 }
14297
14298 int
14299 AnalyzeModeEvent ()
14300 {
14301     char buf[MSG_SIZ];
14302
14303     if (!first.analysisSupport) {
14304       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14305       DisplayError(buf, 0);
14306       return 0;
14307     }
14308     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14309     if (appData.icsActive) {
14310         if (gameMode != IcsObserving) {
14311           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14312             DisplayError(buf, 0);
14313             /* secure check */
14314             if (appData.icsEngineAnalyze) {
14315                 if (appData.debugMode)
14316                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14317                 ExitAnalyzeMode();
14318                 ModeHighlight();
14319             }
14320             return 0;
14321         }
14322         /* if enable, user wants to disable icsEngineAnalyze */
14323         if (appData.icsEngineAnalyze) {
14324                 ExitAnalyzeMode();
14325                 ModeHighlight();
14326                 return 0;
14327         }
14328         appData.icsEngineAnalyze = TRUE;
14329         if (appData.debugMode)
14330             fprintf(debugFP, "ICS engine analyze starting... \n");
14331     }
14332
14333     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14334     if (appData.noChessProgram || gameMode == AnalyzeMode)
14335       return 0;
14336
14337     if (gameMode != AnalyzeFile) {
14338         if (!appData.icsEngineAnalyze) {
14339                EditGameEvent();
14340                if (gameMode != EditGame) return 0;
14341         }
14342         if (!appData.showThinking) ToggleShowThinking();
14343         ResurrectChessProgram();
14344         SendToProgram("analyze\n", &first);
14345         first.analyzing = TRUE;
14346         /*first.maybeThinking = TRUE;*/
14347         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14348         EngineOutputPopUp();
14349     }
14350     if (!appData.icsEngineAnalyze) {
14351         gameMode = AnalyzeMode;
14352         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14353     }
14354     pausing = FALSE;
14355     ModeHighlight();
14356     SetGameInfo();
14357
14358     StartAnalysisClock();
14359     GetTimeMark(&lastNodeCountTime);
14360     lastNodeCount = 0;
14361     return 1;
14362 }
14363
14364 void
14365 AnalyzeFileEvent ()
14366 {
14367     if (appData.noChessProgram || gameMode == AnalyzeFile)
14368       return;
14369
14370     if (!first.analysisSupport) {
14371       char buf[MSG_SIZ];
14372       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14373       DisplayError(buf, 0);
14374       return;
14375     }
14376
14377     if (gameMode != AnalyzeMode) {
14378         keepInfo = 1; // mere annotating should not alter PGN tags
14379         EditGameEvent();
14380         keepInfo = 0;
14381         if (gameMode != EditGame) return;
14382         if (!appData.showThinking) ToggleShowThinking();
14383         ResurrectChessProgram();
14384         SendToProgram("analyze\n", &first);
14385         first.analyzing = TRUE;
14386         /*first.maybeThinking = TRUE;*/
14387         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14388         EngineOutputPopUp();
14389     }
14390     gameMode = AnalyzeFile;
14391     pausing = FALSE;
14392     ModeHighlight();
14393
14394     StartAnalysisClock();
14395     GetTimeMark(&lastNodeCountTime);
14396     lastNodeCount = 0;
14397     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14398     AnalysisPeriodicEvent(1);
14399 }
14400
14401 void
14402 MachineWhiteEvent ()
14403 {
14404     char buf[MSG_SIZ];
14405     char *bookHit = NULL;
14406
14407     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14408       return;
14409
14410
14411     if (gameMode == PlayFromGameFile ||
14412         gameMode == TwoMachinesPlay  ||
14413         gameMode == Training         ||
14414         gameMode == AnalyzeMode      ||
14415         gameMode == EndOfGame)
14416         EditGameEvent();
14417
14418     if (gameMode == EditPosition)
14419         EditPositionDone(TRUE);
14420
14421     if (!WhiteOnMove(currentMove)) {
14422         DisplayError(_("It is not White's turn"), 0);
14423         return;
14424     }
14425
14426     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14427       ExitAnalyzeMode();
14428
14429     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14430         gameMode == AnalyzeFile)
14431         TruncateGame();
14432
14433     ResurrectChessProgram();    /* in case it isn't running */
14434     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14435         gameMode = MachinePlaysWhite;
14436         ResetClocks();
14437     } else
14438     gameMode = MachinePlaysWhite;
14439     pausing = FALSE;
14440     ModeHighlight();
14441     SetGameInfo();
14442     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14443     DisplayTitle(buf);
14444     if (first.sendName) {
14445       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14446       SendToProgram(buf, &first);
14447     }
14448     if (first.sendTime) {
14449       if (first.useColors) {
14450         SendToProgram("black\n", &first); /*gnu kludge*/
14451       }
14452       SendTimeRemaining(&first, TRUE);
14453     }
14454     if (first.useColors) {
14455       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14456     }
14457     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14458     SetMachineThinkingEnables();
14459     first.maybeThinking = TRUE;
14460     StartClocks();
14461     firstMove = FALSE;
14462
14463     if (appData.autoFlipView && !flipView) {
14464       flipView = !flipView;
14465       DrawPosition(FALSE, NULL);
14466       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14467     }
14468
14469     if(bookHit) { // [HGM] book: simulate book reply
14470         static char bookMove[MSG_SIZ]; // a bit generous?
14471
14472         programStats.nodes = programStats.depth = programStats.time =
14473         programStats.score = programStats.got_only_move = 0;
14474         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14475
14476         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14477         strcat(bookMove, bookHit);
14478         HandleMachineMove(bookMove, &first);
14479     }
14480 }
14481
14482 void
14483 MachineBlackEvent ()
14484 {
14485   char buf[MSG_SIZ];
14486   char *bookHit = NULL;
14487
14488     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14489         return;
14490
14491
14492     if (gameMode == PlayFromGameFile ||
14493         gameMode == TwoMachinesPlay  ||
14494         gameMode == Training         ||
14495         gameMode == AnalyzeMode      ||
14496         gameMode == EndOfGame)
14497         EditGameEvent();
14498
14499     if (gameMode == EditPosition)
14500         EditPositionDone(TRUE);
14501
14502     if (WhiteOnMove(currentMove)) {
14503         DisplayError(_("It is not Black's turn"), 0);
14504         return;
14505     }
14506
14507     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14508       ExitAnalyzeMode();
14509
14510     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14511         gameMode == AnalyzeFile)
14512         TruncateGame();
14513
14514     ResurrectChessProgram();    /* in case it isn't running */
14515     gameMode = MachinePlaysBlack;
14516     pausing = FALSE;
14517     ModeHighlight();
14518     SetGameInfo();
14519     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14520     DisplayTitle(buf);
14521     if (first.sendName) {
14522       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14523       SendToProgram(buf, &first);
14524     }
14525     if (first.sendTime) {
14526       if (first.useColors) {
14527         SendToProgram("white\n", &first); /*gnu kludge*/
14528       }
14529       SendTimeRemaining(&first, FALSE);
14530     }
14531     if (first.useColors) {
14532       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14533     }
14534     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14535     SetMachineThinkingEnables();
14536     first.maybeThinking = TRUE;
14537     StartClocks();
14538
14539     if (appData.autoFlipView && flipView) {
14540       flipView = !flipView;
14541       DrawPosition(FALSE, NULL);
14542       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14543     }
14544     if(bookHit) { // [HGM] book: simulate book reply
14545         static char bookMove[MSG_SIZ]; // a bit generous?
14546
14547         programStats.nodes = programStats.depth = programStats.time =
14548         programStats.score = programStats.got_only_move = 0;
14549         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14550
14551         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14552         strcat(bookMove, bookHit);
14553         HandleMachineMove(bookMove, &first);
14554     }
14555 }
14556
14557
14558 void
14559 DisplayTwoMachinesTitle ()
14560 {
14561     char buf[MSG_SIZ];
14562     if (appData.matchGames > 0) {
14563         if(appData.tourneyFile[0]) {
14564           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14565                    gameInfo.white, _("vs."), gameInfo.black,
14566                    nextGame+1, appData.matchGames+1,
14567                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14568         } else
14569         if (first.twoMachinesColor[0] == 'w') {
14570           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14571                    gameInfo.white, _("vs."),  gameInfo.black,
14572                    first.matchWins, second.matchWins,
14573                    matchGame - 1 - (first.matchWins + second.matchWins));
14574         } else {
14575           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14576                    gameInfo.white, _("vs."), gameInfo.black,
14577                    second.matchWins, first.matchWins,
14578                    matchGame - 1 - (first.matchWins + second.matchWins));
14579         }
14580     } else {
14581       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14582     }
14583     DisplayTitle(buf);
14584 }
14585
14586 void
14587 SettingsMenuIfReady ()
14588 {
14589   if (second.lastPing != second.lastPong) {
14590     DisplayMessage("", _("Waiting for second chess program"));
14591     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14592     return;
14593   }
14594   ThawUI();
14595   DisplayMessage("", "");
14596   SettingsPopUp(&second);
14597 }
14598
14599 int
14600 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14601 {
14602     char buf[MSG_SIZ];
14603     if (cps->pr == NoProc) {
14604         StartChessProgram(cps);
14605         if (cps->protocolVersion == 1) {
14606           retry();
14607           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14608         } else {
14609           /* kludge: allow timeout for initial "feature" command */
14610           if(retry != TwoMachinesEventIfReady) FreezeUI();
14611           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14612           DisplayMessage("", buf);
14613           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14614         }
14615         return 1;
14616     }
14617     return 0;
14618 }
14619
14620 void
14621 TwoMachinesEvent P((void))
14622 {
14623     int i;
14624     char buf[MSG_SIZ];
14625     ChessProgramState *onmove;
14626     char *bookHit = NULL;
14627     static int stalling = 0;
14628     TimeMark now;
14629     long wait;
14630
14631     if (appData.noChessProgram) return;
14632
14633     switch (gameMode) {
14634       case TwoMachinesPlay:
14635         return;
14636       case MachinePlaysWhite:
14637       case MachinePlaysBlack:
14638         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14639             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14640             return;
14641         }
14642         /* fall through */
14643       case BeginningOfGame:
14644       case PlayFromGameFile:
14645       case EndOfGame:
14646         EditGameEvent();
14647         if (gameMode != EditGame) return;
14648         break;
14649       case EditPosition:
14650         EditPositionDone(TRUE);
14651         break;
14652       case AnalyzeMode:
14653       case AnalyzeFile:
14654         ExitAnalyzeMode();
14655         break;
14656       case EditGame:
14657       default:
14658         break;
14659     }
14660
14661 //    forwardMostMove = currentMove;
14662     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14663     startingEngine = TRUE;
14664
14665     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14666
14667     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14668     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14669       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14670       return;
14671     }
14672     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14673
14674     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14675                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14676         startingEngine = matchMode = FALSE;
14677         DisplayError("second engine does not play this", 0);
14678         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14679         EditGameEvent(); // switch back to EditGame mode
14680         return;
14681     }
14682
14683     if(!stalling) {
14684       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14685       SendToProgram("force\n", &second);
14686       stalling = 1;
14687       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14688       return;
14689     }
14690     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14691     if(appData.matchPause>10000 || appData.matchPause<10)
14692                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14693     wait = SubtractTimeMarks(&now, &pauseStart);
14694     if(wait < appData.matchPause) {
14695         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14696         return;
14697     }
14698     // we are now committed to starting the game
14699     stalling = 0;
14700     DisplayMessage("", "");
14701     if (startedFromSetupPosition) {
14702         SendBoard(&second, backwardMostMove);
14703     if (appData.debugMode) {
14704         fprintf(debugFP, "Two Machines\n");
14705     }
14706     }
14707     for (i = backwardMostMove; i < forwardMostMove; i++) {
14708         SendMoveToProgram(i, &second);
14709     }
14710
14711     gameMode = TwoMachinesPlay;
14712     pausing = startingEngine = FALSE;
14713     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14714     SetGameInfo();
14715     DisplayTwoMachinesTitle();
14716     firstMove = TRUE;
14717     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14718         onmove = &first;
14719     } else {
14720         onmove = &second;
14721     }
14722     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14723     SendToProgram(first.computerString, &first);
14724     if (first.sendName) {
14725       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14726       SendToProgram(buf, &first);
14727     }
14728     SendToProgram(second.computerString, &second);
14729     if (second.sendName) {
14730       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14731       SendToProgram(buf, &second);
14732     }
14733
14734     ResetClocks();
14735     if (!first.sendTime || !second.sendTime) {
14736         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14737         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14738     }
14739     if (onmove->sendTime) {
14740       if (onmove->useColors) {
14741         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14742       }
14743       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14744     }
14745     if (onmove->useColors) {
14746       SendToProgram(onmove->twoMachinesColor, onmove);
14747     }
14748     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14749 //    SendToProgram("go\n", onmove);
14750     onmove->maybeThinking = TRUE;
14751     SetMachineThinkingEnables();
14752
14753     StartClocks();
14754
14755     if(bookHit) { // [HGM] book: simulate book reply
14756         static char bookMove[MSG_SIZ]; // a bit generous?
14757
14758         programStats.nodes = programStats.depth = programStats.time =
14759         programStats.score = programStats.got_only_move = 0;
14760         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14761
14762         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14763         strcat(bookMove, bookHit);
14764         savedMessage = bookMove; // args for deferred call
14765         savedState = onmove;
14766         ScheduleDelayedEvent(DeferredBookMove, 1);
14767     }
14768 }
14769
14770 void
14771 TrainingEvent ()
14772 {
14773     if (gameMode == Training) {
14774       SetTrainingModeOff();
14775       gameMode = PlayFromGameFile;
14776       DisplayMessage("", _("Training mode off"));
14777     } else {
14778       gameMode = Training;
14779       animateTraining = appData.animate;
14780
14781       /* make sure we are not already at the end of the game */
14782       if (currentMove < forwardMostMove) {
14783         SetTrainingModeOn();
14784         DisplayMessage("", _("Training mode on"));
14785       } else {
14786         gameMode = PlayFromGameFile;
14787         DisplayError(_("Already at end of game"), 0);
14788       }
14789     }
14790     ModeHighlight();
14791 }
14792
14793 void
14794 IcsClientEvent ()
14795 {
14796     if (!appData.icsActive) return;
14797     switch (gameMode) {
14798       case IcsPlayingWhite:
14799       case IcsPlayingBlack:
14800       case IcsObserving:
14801       case IcsIdle:
14802       case BeginningOfGame:
14803       case IcsExamining:
14804         return;
14805
14806       case EditGame:
14807         break;
14808
14809       case EditPosition:
14810         EditPositionDone(TRUE);
14811         break;
14812
14813       case AnalyzeMode:
14814       case AnalyzeFile:
14815         ExitAnalyzeMode();
14816         break;
14817
14818       default:
14819         EditGameEvent();
14820         break;
14821     }
14822
14823     gameMode = IcsIdle;
14824     ModeHighlight();
14825     return;
14826 }
14827
14828 void
14829 EditGameEvent ()
14830 {
14831     int i;
14832
14833     switch (gameMode) {
14834       case Training:
14835         SetTrainingModeOff();
14836         break;
14837       case MachinePlaysWhite:
14838       case MachinePlaysBlack:
14839       case BeginningOfGame:
14840         SendToProgram("force\n", &first);
14841         SetUserThinkingEnables();
14842         break;
14843       case PlayFromGameFile:
14844         (void) StopLoadGameTimer();
14845         if (gameFileFP != NULL) {
14846             gameFileFP = NULL;
14847         }
14848         break;
14849       case EditPosition:
14850         EditPositionDone(TRUE);
14851         break;
14852       case AnalyzeMode:
14853       case AnalyzeFile:
14854         ExitAnalyzeMode();
14855         SendToProgram("force\n", &first);
14856         break;
14857       case TwoMachinesPlay:
14858         GameEnds(EndOfFile, NULL, GE_PLAYER);
14859         ResurrectChessProgram();
14860         SetUserThinkingEnables();
14861         break;
14862       case EndOfGame:
14863         ResurrectChessProgram();
14864         break;
14865       case IcsPlayingBlack:
14866       case IcsPlayingWhite:
14867         DisplayError(_("Warning: You are still playing a game"), 0);
14868         break;
14869       case IcsObserving:
14870         DisplayError(_("Warning: You are still observing a game"), 0);
14871         break;
14872       case IcsExamining:
14873         DisplayError(_("Warning: You are still examining a game"), 0);
14874         break;
14875       case IcsIdle:
14876         break;
14877       case EditGame:
14878       default:
14879         return;
14880     }
14881
14882     pausing = FALSE;
14883     StopClocks();
14884     first.offeredDraw = second.offeredDraw = 0;
14885
14886     if (gameMode == PlayFromGameFile) {
14887         whiteTimeRemaining = timeRemaining[0][currentMove];
14888         blackTimeRemaining = timeRemaining[1][currentMove];
14889         DisplayTitle("");
14890     }
14891
14892     if (gameMode == MachinePlaysWhite ||
14893         gameMode == MachinePlaysBlack ||
14894         gameMode == TwoMachinesPlay ||
14895         gameMode == EndOfGame) {
14896         i = forwardMostMove;
14897         while (i > currentMove) {
14898             SendToProgram("undo\n", &first);
14899             i--;
14900         }
14901         if(!adjustedClock) {
14902         whiteTimeRemaining = timeRemaining[0][currentMove];
14903         blackTimeRemaining = timeRemaining[1][currentMove];
14904         DisplayBothClocks();
14905         }
14906         if (whiteFlag || blackFlag) {
14907             whiteFlag = blackFlag = 0;
14908         }
14909         DisplayTitle("");
14910     }
14911
14912     gameMode = EditGame;
14913     ModeHighlight();
14914     SetGameInfo();
14915 }
14916
14917
14918 void
14919 EditPositionEvent ()
14920 {
14921     if (gameMode == EditPosition) {
14922         EditGameEvent();
14923         return;
14924     }
14925
14926     EditGameEvent();
14927     if (gameMode != EditGame) return;
14928
14929     gameMode = EditPosition;
14930     ModeHighlight();
14931     SetGameInfo();
14932     if (currentMove > 0)
14933       CopyBoard(boards[0], boards[currentMove]);
14934
14935     blackPlaysFirst = !WhiteOnMove(currentMove);
14936     ResetClocks();
14937     currentMove = forwardMostMove = backwardMostMove = 0;
14938     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14939     DisplayMove(-1);
14940     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14941 }
14942
14943 void
14944 ExitAnalyzeMode ()
14945 {
14946     /* [DM] icsEngineAnalyze - possible call from other functions */
14947     if (appData.icsEngineAnalyze) {
14948         appData.icsEngineAnalyze = FALSE;
14949
14950         DisplayMessage("",_("Close ICS engine analyze..."));
14951     }
14952     if (first.analysisSupport && first.analyzing) {
14953       SendToBoth("exit\n");
14954       first.analyzing = second.analyzing = FALSE;
14955     }
14956     thinkOutput[0] = NULLCHAR;
14957 }
14958
14959 void
14960 EditPositionDone (Boolean fakeRights)
14961 {
14962     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14963
14964     startedFromSetupPosition = TRUE;
14965     InitChessProgram(&first, FALSE);
14966     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14967       boards[0][EP_STATUS] = EP_NONE;
14968       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14969       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14970         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14971         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14972       } else boards[0][CASTLING][2] = NoRights;
14973       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14974         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14975         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14976       } else boards[0][CASTLING][5] = NoRights;
14977       if(gameInfo.variant == VariantSChess) {
14978         int i;
14979         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14980           boards[0][VIRGIN][i] = 0;
14981           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14982           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14983         }
14984       }
14985     }
14986     SendToProgram("force\n", &first);
14987     if (blackPlaysFirst) {
14988         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14989         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14990         currentMove = forwardMostMove = backwardMostMove = 1;
14991         CopyBoard(boards[1], boards[0]);
14992     } else {
14993         currentMove = forwardMostMove = backwardMostMove = 0;
14994     }
14995     SendBoard(&first, forwardMostMove);
14996     if (appData.debugMode) {
14997         fprintf(debugFP, "EditPosDone\n");
14998     }
14999     DisplayTitle("");
15000     DisplayMessage("", "");
15001     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15002     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15003     gameMode = EditGame;
15004     ModeHighlight();
15005     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15006     ClearHighlights(); /* [AS] */
15007 }
15008
15009 /* Pause for `ms' milliseconds */
15010 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15011 void
15012 TimeDelay (long ms)
15013 {
15014     TimeMark m1, m2;
15015
15016     GetTimeMark(&m1);
15017     do {
15018         GetTimeMark(&m2);
15019     } while (SubtractTimeMarks(&m2, &m1) < ms);
15020 }
15021
15022 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15023 void
15024 SendMultiLineToICS (char *buf)
15025 {
15026     char temp[MSG_SIZ+1], *p;
15027     int len;
15028
15029     len = strlen(buf);
15030     if (len > MSG_SIZ)
15031       len = MSG_SIZ;
15032
15033     strncpy(temp, buf, len);
15034     temp[len] = 0;
15035
15036     p = temp;
15037     while (*p) {
15038         if (*p == '\n' || *p == '\r')
15039           *p = ' ';
15040         ++p;
15041     }
15042
15043     strcat(temp, "\n");
15044     SendToICS(temp);
15045     SendToPlayer(temp, strlen(temp));
15046 }
15047
15048 void
15049 SetWhiteToPlayEvent ()
15050 {
15051     if (gameMode == EditPosition) {
15052         blackPlaysFirst = FALSE;
15053         DisplayBothClocks();    /* works because currentMove is 0 */
15054     } else if (gameMode == IcsExamining) {
15055         SendToICS(ics_prefix);
15056         SendToICS("tomove white\n");
15057     }
15058 }
15059
15060 void
15061 SetBlackToPlayEvent ()
15062 {
15063     if (gameMode == EditPosition) {
15064         blackPlaysFirst = TRUE;
15065         currentMove = 1;        /* kludge */
15066         DisplayBothClocks();
15067         currentMove = 0;
15068     } else if (gameMode == IcsExamining) {
15069         SendToICS(ics_prefix);
15070         SendToICS("tomove black\n");
15071     }
15072 }
15073
15074 void
15075 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15076 {
15077     char buf[MSG_SIZ];
15078     ChessSquare piece = boards[0][y][x];
15079     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15080     static int lastVariant;
15081
15082     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15083
15084     switch (selection) {
15085       case ClearBoard:
15086         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15087         MarkTargetSquares(1);
15088         CopyBoard(currentBoard, boards[0]);
15089         CopyBoard(menuBoard, initialPosition);
15090         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15091             SendToICS(ics_prefix);
15092             SendToICS("bsetup clear\n");
15093         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15094             SendToICS(ics_prefix);
15095             SendToICS("clearboard\n");
15096         } else {
15097             int nonEmpty = 0;
15098             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15099                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15100                 for (y = 0; y < BOARD_HEIGHT; y++) {
15101                     if (gameMode == IcsExamining) {
15102                         if (boards[currentMove][y][x] != EmptySquare) {
15103                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15104                                     AAA + x, ONE + y);
15105                             SendToICS(buf);
15106                         }
15107                     } else {
15108                         if(boards[0][y][x] != p) nonEmpty++;
15109                         boards[0][y][x] = p;
15110                     }
15111                 }
15112             }
15113             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15114                 int r;
15115                 for(r = 0; r < BOARD_HEIGHT; r++) {
15116                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15117                     ChessSquare p = menuBoard[r][x];
15118                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15119                   }
15120                 }
15121                 DisplayMessage("Clicking clock again restores position", "");
15122                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15123                 if(!nonEmpty) { // asked to clear an empty board
15124                     CopyBoard(boards[0], menuBoard);
15125                 } else
15126                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15127                     CopyBoard(boards[0], initialPosition);
15128                 } else
15129                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15130                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15131                     CopyBoard(boards[0], erasedBoard);
15132                 } else
15133                     CopyBoard(erasedBoard, currentBoard);
15134
15135             }
15136         }
15137         if (gameMode == EditPosition) {
15138             DrawPosition(FALSE, boards[0]);
15139         }
15140         break;
15141
15142       case WhitePlay:
15143         SetWhiteToPlayEvent();
15144         break;
15145
15146       case BlackPlay:
15147         SetBlackToPlayEvent();
15148         break;
15149
15150       case EmptySquare:
15151         if (gameMode == IcsExamining) {
15152             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15153             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15154             SendToICS(buf);
15155         } else {
15156             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15157                 if(x == BOARD_LEFT-2) {
15158                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15159                     boards[0][y][1] = 0;
15160                 } else
15161                 if(x == BOARD_RGHT+1) {
15162                     if(y >= gameInfo.holdingsSize) break;
15163                     boards[0][y][BOARD_WIDTH-2] = 0;
15164                 } else break;
15165             }
15166             boards[0][y][x] = EmptySquare;
15167             DrawPosition(FALSE, boards[0]);
15168         }
15169         break;
15170
15171       case PromotePiece:
15172         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15173            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15174             selection = (ChessSquare) (PROMOTED piece);
15175         } else if(piece == EmptySquare) selection = WhiteSilver;
15176         else selection = (ChessSquare)((int)piece - 1);
15177         goto defaultlabel;
15178
15179       case DemotePiece:
15180         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15181            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15182             selection = (ChessSquare) (DEMOTED piece);
15183         } else if(piece == EmptySquare) selection = BlackSilver;
15184         else selection = (ChessSquare)((int)piece + 1);
15185         goto defaultlabel;
15186
15187       case WhiteQueen:
15188       case BlackQueen:
15189         if(gameInfo.variant == VariantShatranj ||
15190            gameInfo.variant == VariantXiangqi  ||
15191            gameInfo.variant == VariantCourier  ||
15192            gameInfo.variant == VariantASEAN    ||
15193            gameInfo.variant == VariantMakruk     )
15194             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15195         goto defaultlabel;
15196
15197       case WhiteKing:
15198       case BlackKing:
15199         if(gameInfo.variant == VariantXiangqi)
15200             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15201         if(gameInfo.variant == VariantKnightmate)
15202             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15203       default:
15204         defaultlabel:
15205         if (gameMode == IcsExamining) {
15206             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15207             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15208                      PieceToChar(selection), AAA + x, ONE + y);
15209             SendToICS(buf);
15210         } else {
15211             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15212                 int n;
15213                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15214                     n = PieceToNumber(selection - BlackPawn);
15215                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15216                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15217                     boards[0][BOARD_HEIGHT-1-n][1]++;
15218                 } else
15219                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15220                     n = PieceToNumber(selection);
15221                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15222                     boards[0][n][BOARD_WIDTH-1] = selection;
15223                     boards[0][n][BOARD_WIDTH-2]++;
15224                 }
15225             } else
15226             boards[0][y][x] = selection;
15227             DrawPosition(TRUE, boards[0]);
15228             ClearHighlights();
15229             fromX = fromY = -1;
15230         }
15231         break;
15232     }
15233 }
15234
15235
15236 void
15237 DropMenuEvent (ChessSquare selection, int x, int y)
15238 {
15239     ChessMove moveType;
15240
15241     switch (gameMode) {
15242       case IcsPlayingWhite:
15243       case MachinePlaysBlack:
15244         if (!WhiteOnMove(currentMove)) {
15245             DisplayMoveError(_("It is Black's turn"));
15246             return;
15247         }
15248         moveType = WhiteDrop;
15249         break;
15250       case IcsPlayingBlack:
15251       case MachinePlaysWhite:
15252         if (WhiteOnMove(currentMove)) {
15253             DisplayMoveError(_("It is White's turn"));
15254             return;
15255         }
15256         moveType = BlackDrop;
15257         break;
15258       case EditGame:
15259         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15260         break;
15261       default:
15262         return;
15263     }
15264
15265     if (moveType == BlackDrop && selection < BlackPawn) {
15266       selection = (ChessSquare) ((int) selection
15267                                  + (int) BlackPawn - (int) WhitePawn);
15268     }
15269     if (boards[currentMove][y][x] != EmptySquare) {
15270         DisplayMoveError(_("That square is occupied"));
15271         return;
15272     }
15273
15274     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15275 }
15276
15277 void
15278 AcceptEvent ()
15279 {
15280     /* Accept a pending offer of any kind from opponent */
15281
15282     if (appData.icsActive) {
15283         SendToICS(ics_prefix);
15284         SendToICS("accept\n");
15285     } else if (cmailMsgLoaded) {
15286         if (currentMove == cmailOldMove &&
15287             commentList[cmailOldMove] != NULL &&
15288             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15289                    "Black offers a draw" : "White offers a draw")) {
15290             TruncateGame();
15291             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15292             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15293         } else {
15294             DisplayError(_("There is no pending offer on this move"), 0);
15295             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15296         }
15297     } else {
15298         /* Not used for offers from chess program */
15299     }
15300 }
15301
15302 void
15303 DeclineEvent ()
15304 {
15305     /* Decline a pending offer of any kind from opponent */
15306
15307     if (appData.icsActive) {
15308         SendToICS(ics_prefix);
15309         SendToICS("decline\n");
15310     } else if (cmailMsgLoaded) {
15311         if (currentMove == cmailOldMove &&
15312             commentList[cmailOldMove] != NULL &&
15313             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15314                    "Black offers a draw" : "White offers a draw")) {
15315 #ifdef NOTDEF
15316             AppendComment(cmailOldMove, "Draw declined", TRUE);
15317             DisplayComment(cmailOldMove - 1, "Draw declined");
15318 #endif /*NOTDEF*/
15319         } else {
15320             DisplayError(_("There is no pending offer on this move"), 0);
15321         }
15322     } else {
15323         /* Not used for offers from chess program */
15324     }
15325 }
15326
15327 void
15328 RematchEvent ()
15329 {
15330     /* Issue ICS rematch command */
15331     if (appData.icsActive) {
15332         SendToICS(ics_prefix);
15333         SendToICS("rematch\n");
15334     }
15335 }
15336
15337 void
15338 CallFlagEvent ()
15339 {
15340     /* Call your opponent's flag (claim a win on time) */
15341     if (appData.icsActive) {
15342         SendToICS(ics_prefix);
15343         SendToICS("flag\n");
15344     } else {
15345         switch (gameMode) {
15346           default:
15347             return;
15348           case MachinePlaysWhite:
15349             if (whiteFlag) {
15350                 if (blackFlag)
15351                   GameEnds(GameIsDrawn, "Both players ran out of time",
15352                            GE_PLAYER);
15353                 else
15354                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15355             } else {
15356                 DisplayError(_("Your opponent is not out of time"), 0);
15357             }
15358             break;
15359           case MachinePlaysBlack:
15360             if (blackFlag) {
15361                 if (whiteFlag)
15362                   GameEnds(GameIsDrawn, "Both players ran out of time",
15363                            GE_PLAYER);
15364                 else
15365                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15366             } else {
15367                 DisplayError(_("Your opponent is not out of time"), 0);
15368             }
15369             break;
15370         }
15371     }
15372 }
15373
15374 void
15375 ClockClick (int which)
15376 {       // [HGM] code moved to back-end from winboard.c
15377         if(which) { // black clock
15378           if (gameMode == EditPosition || gameMode == IcsExamining) {
15379             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15380             SetBlackToPlayEvent();
15381           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15382                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15383           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15384           } else if (shiftKey) {
15385             AdjustClock(which, -1);
15386           } else if (gameMode == IcsPlayingWhite ||
15387                      gameMode == MachinePlaysBlack) {
15388             CallFlagEvent();
15389           }
15390         } else { // white clock
15391           if (gameMode == EditPosition || gameMode == IcsExamining) {
15392             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15393             SetWhiteToPlayEvent();
15394           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15395                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15396           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15397           } else if (shiftKey) {
15398             AdjustClock(which, -1);
15399           } else if (gameMode == IcsPlayingBlack ||
15400                    gameMode == MachinePlaysWhite) {
15401             CallFlagEvent();
15402           }
15403         }
15404 }
15405
15406 void
15407 DrawEvent ()
15408 {
15409     /* Offer draw or accept pending draw offer from opponent */
15410
15411     if (appData.icsActive) {
15412         /* Note: tournament rules require draw offers to be
15413            made after you make your move but before you punch
15414            your clock.  Currently ICS doesn't let you do that;
15415            instead, you immediately punch your clock after making
15416            a move, but you can offer a draw at any time. */
15417
15418         SendToICS(ics_prefix);
15419         SendToICS("draw\n");
15420         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15421     } else if (cmailMsgLoaded) {
15422         if (currentMove == cmailOldMove &&
15423             commentList[cmailOldMove] != NULL &&
15424             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15425                    "Black offers a draw" : "White offers a draw")) {
15426             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15427             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15428         } else if (currentMove == cmailOldMove + 1) {
15429             char *offer = WhiteOnMove(cmailOldMove) ?
15430               "White offers a draw" : "Black offers a draw";
15431             AppendComment(currentMove, offer, TRUE);
15432             DisplayComment(currentMove - 1, offer);
15433             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15434         } else {
15435             DisplayError(_("You must make your move before offering a draw"), 0);
15436             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15437         }
15438     } else if (first.offeredDraw) {
15439         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15440     } else {
15441         if (first.sendDrawOffers) {
15442             SendToProgram("draw\n", &first);
15443             userOfferedDraw = TRUE;
15444         }
15445     }
15446 }
15447
15448 void
15449 AdjournEvent ()
15450 {
15451     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15452
15453     if (appData.icsActive) {
15454         SendToICS(ics_prefix);
15455         SendToICS("adjourn\n");
15456     } else {
15457         /* Currently GNU Chess doesn't offer or accept Adjourns */
15458     }
15459 }
15460
15461
15462 void
15463 AbortEvent ()
15464 {
15465     /* Offer Abort or accept pending Abort offer from opponent */
15466
15467     if (appData.icsActive) {
15468         SendToICS(ics_prefix);
15469         SendToICS("abort\n");
15470     } else {
15471         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15472     }
15473 }
15474
15475 void
15476 ResignEvent ()
15477 {
15478     /* Resign.  You can do this even if it's not your turn. */
15479
15480     if (appData.icsActive) {
15481         SendToICS(ics_prefix);
15482         SendToICS("resign\n");
15483     } else {
15484         switch (gameMode) {
15485           case MachinePlaysWhite:
15486             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15487             break;
15488           case MachinePlaysBlack:
15489             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15490             break;
15491           case EditGame:
15492             if (cmailMsgLoaded) {
15493                 TruncateGame();
15494                 if (WhiteOnMove(cmailOldMove)) {
15495                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15496                 } else {
15497                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15498                 }
15499                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15500             }
15501             break;
15502           default:
15503             break;
15504         }
15505     }
15506 }
15507
15508
15509 void
15510 StopObservingEvent ()
15511 {
15512     /* Stop observing current games */
15513     SendToICS(ics_prefix);
15514     SendToICS("unobserve\n");
15515 }
15516
15517 void
15518 StopExaminingEvent ()
15519 {
15520     /* Stop observing current game */
15521     SendToICS(ics_prefix);
15522     SendToICS("unexamine\n");
15523 }
15524
15525 void
15526 ForwardInner (int target)
15527 {
15528     int limit; int oldSeekGraphUp = seekGraphUp;
15529
15530     if (appData.debugMode)
15531         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15532                 target, currentMove, forwardMostMove);
15533
15534     if (gameMode == EditPosition)
15535       return;
15536
15537     seekGraphUp = FALSE;
15538     MarkTargetSquares(1);
15539     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15540
15541     if (gameMode == PlayFromGameFile && !pausing)
15542       PauseEvent();
15543
15544     if (gameMode == IcsExamining && pausing)
15545       limit = pauseExamForwardMostMove;
15546     else
15547       limit = forwardMostMove;
15548
15549     if (target > limit) target = limit;
15550
15551     if (target > 0 && moveList[target - 1][0]) {
15552         int fromX, fromY, toX, toY;
15553         toX = moveList[target - 1][2] - AAA;
15554         toY = moveList[target - 1][3] - ONE;
15555         if (moveList[target - 1][1] == '@') {
15556             if (appData.highlightLastMove) {
15557                 SetHighlights(-1, -1, toX, toY);
15558             }
15559         } else {
15560             int viaX = moveList[target - 1][5] - AAA;
15561             int viaY = moveList[target - 1][6] - ONE;
15562             fromX = moveList[target - 1][0] - AAA;
15563             fromY = moveList[target - 1][1] - ONE;
15564             if (target == currentMove + 1) {
15565                 if(moveList[target - 1][4] == ';') { // multi-leg
15566                     ChessSquare piece = boards[currentMove][viaY][viaX];
15567                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15568                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15569                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15570                     boards[currentMove][viaY][viaX] = piece;
15571                 } else
15572                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15573             }
15574             if (appData.highlightLastMove) {
15575                 SetHighlights(fromX, fromY, toX, toY);
15576             }
15577         }
15578     }
15579     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15580         gameMode == Training || gameMode == PlayFromGameFile ||
15581         gameMode == AnalyzeFile) {
15582         while (currentMove < target) {
15583             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15584             SendMoveToProgram(currentMove++, &first);
15585         }
15586     } else {
15587         currentMove = target;
15588     }
15589
15590     if (gameMode == EditGame || gameMode == EndOfGame) {
15591         whiteTimeRemaining = timeRemaining[0][currentMove];
15592         blackTimeRemaining = timeRemaining[1][currentMove];
15593     }
15594     DisplayBothClocks();
15595     DisplayMove(currentMove - 1);
15596     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15597     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15598     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15599         DisplayComment(currentMove - 1, commentList[currentMove]);
15600     }
15601     ClearMap(); // [HGM] exclude: invalidate map
15602 }
15603
15604
15605 void
15606 ForwardEvent ()
15607 {
15608     if (gameMode == IcsExamining && !pausing) {
15609         SendToICS(ics_prefix);
15610         SendToICS("forward\n");
15611     } else {
15612         ForwardInner(currentMove + 1);
15613     }
15614 }
15615
15616 void
15617 ToEndEvent ()
15618 {
15619     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15620         /* to optimze, we temporarily turn off analysis mode while we feed
15621          * the remaining moves to the engine. Otherwise we get analysis output
15622          * after each move.
15623          */
15624         if (first.analysisSupport) {
15625           SendToProgram("exit\nforce\n", &first);
15626           first.analyzing = FALSE;
15627         }
15628     }
15629
15630     if (gameMode == IcsExamining && !pausing) {
15631         SendToICS(ics_prefix);
15632         SendToICS("forward 999999\n");
15633     } else {
15634         ForwardInner(forwardMostMove);
15635     }
15636
15637     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15638         /* we have fed all the moves, so reactivate analysis mode */
15639         SendToProgram("analyze\n", &first);
15640         first.analyzing = TRUE;
15641         /*first.maybeThinking = TRUE;*/
15642         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15643     }
15644 }
15645
15646 void
15647 BackwardInner (int target)
15648 {
15649     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15650
15651     if (appData.debugMode)
15652         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15653                 target, currentMove, forwardMostMove);
15654
15655     if (gameMode == EditPosition) return;
15656     seekGraphUp = FALSE;
15657     MarkTargetSquares(1);
15658     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15659     if (currentMove <= backwardMostMove) {
15660         ClearHighlights();
15661         DrawPosition(full_redraw, boards[currentMove]);
15662         return;
15663     }
15664     if (gameMode == PlayFromGameFile && !pausing)
15665       PauseEvent();
15666
15667     if (moveList[target][0]) {
15668         int fromX, fromY, toX, toY;
15669         toX = moveList[target][2] - AAA;
15670         toY = moveList[target][3] - ONE;
15671         if (moveList[target][1] == '@') {
15672             if (appData.highlightLastMove) {
15673                 SetHighlights(-1, -1, toX, toY);
15674             }
15675         } else {
15676             fromX = moveList[target][0] - AAA;
15677             fromY = moveList[target][1] - ONE;
15678             if (target == currentMove - 1) {
15679                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15680             }
15681             if (appData.highlightLastMove) {
15682                 SetHighlights(fromX, fromY, toX, toY);
15683             }
15684         }
15685     }
15686     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15687         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15688         while (currentMove > target) {
15689             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15690                 // null move cannot be undone. Reload program with move history before it.
15691                 int i;
15692                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15693                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15694                 }
15695                 SendBoard(&first, i);
15696               if(second.analyzing) SendBoard(&second, i);
15697                 for(currentMove=i; currentMove<target; currentMove++) {
15698                     SendMoveToProgram(currentMove, &first);
15699                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15700                 }
15701                 break;
15702             }
15703             SendToBoth("undo\n");
15704             currentMove--;
15705         }
15706     } else {
15707         currentMove = target;
15708     }
15709
15710     if (gameMode == EditGame || gameMode == EndOfGame) {
15711         whiteTimeRemaining = timeRemaining[0][currentMove];
15712         blackTimeRemaining = timeRemaining[1][currentMove];
15713     }
15714     DisplayBothClocks();
15715     DisplayMove(currentMove - 1);
15716     DrawPosition(full_redraw, boards[currentMove]);
15717     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15718     // [HGM] PV info: routine tests if comment empty
15719     DisplayComment(currentMove - 1, commentList[currentMove]);
15720     ClearMap(); // [HGM] exclude: invalidate map
15721 }
15722
15723 void
15724 BackwardEvent ()
15725 {
15726     if (gameMode == IcsExamining && !pausing) {
15727         SendToICS(ics_prefix);
15728         SendToICS("backward\n");
15729     } else {
15730         BackwardInner(currentMove - 1);
15731     }
15732 }
15733
15734 void
15735 ToStartEvent ()
15736 {
15737     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15738         /* to optimize, we temporarily turn off analysis mode while we undo
15739          * all the moves. Otherwise we get analysis output after each undo.
15740          */
15741         if (first.analysisSupport) {
15742           SendToProgram("exit\nforce\n", &first);
15743           first.analyzing = FALSE;
15744         }
15745     }
15746
15747     if (gameMode == IcsExamining && !pausing) {
15748         SendToICS(ics_prefix);
15749         SendToICS("backward 999999\n");
15750     } else {
15751         BackwardInner(backwardMostMove);
15752     }
15753
15754     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15755         /* we have fed all the moves, so reactivate analysis mode */
15756         SendToProgram("analyze\n", &first);
15757         first.analyzing = TRUE;
15758         /*first.maybeThinking = TRUE;*/
15759         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15760     }
15761 }
15762
15763 void
15764 ToNrEvent (int to)
15765 {
15766   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15767   if (to >= forwardMostMove) to = forwardMostMove;
15768   if (to <= backwardMostMove) to = backwardMostMove;
15769   if (to < currentMove) {
15770     BackwardInner(to);
15771   } else {
15772     ForwardInner(to);
15773   }
15774 }
15775
15776 void
15777 RevertEvent (Boolean annotate)
15778 {
15779     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15780         return;
15781     }
15782     if (gameMode != IcsExamining) {
15783         DisplayError(_("You are not examining a game"), 0);
15784         return;
15785     }
15786     if (pausing) {
15787         DisplayError(_("You can't revert while pausing"), 0);
15788         return;
15789     }
15790     SendToICS(ics_prefix);
15791     SendToICS("revert\n");
15792 }
15793
15794 void
15795 RetractMoveEvent ()
15796 {
15797     switch (gameMode) {
15798       case MachinePlaysWhite:
15799       case MachinePlaysBlack:
15800         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15801             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15802             return;
15803         }
15804         if (forwardMostMove < 2) return;
15805         currentMove = forwardMostMove = forwardMostMove - 2;
15806         whiteTimeRemaining = timeRemaining[0][currentMove];
15807         blackTimeRemaining = timeRemaining[1][currentMove];
15808         DisplayBothClocks();
15809         DisplayMove(currentMove - 1);
15810         ClearHighlights();/*!! could figure this out*/
15811         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15812         SendToProgram("remove\n", &first);
15813         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15814         break;
15815
15816       case BeginningOfGame:
15817       default:
15818         break;
15819
15820       case IcsPlayingWhite:
15821       case IcsPlayingBlack:
15822         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15823             SendToICS(ics_prefix);
15824             SendToICS("takeback 2\n");
15825         } else {
15826             SendToICS(ics_prefix);
15827             SendToICS("takeback 1\n");
15828         }
15829         break;
15830     }
15831 }
15832
15833 void
15834 MoveNowEvent ()
15835 {
15836     ChessProgramState *cps;
15837
15838     switch (gameMode) {
15839       case MachinePlaysWhite:
15840         if (!WhiteOnMove(forwardMostMove)) {
15841             DisplayError(_("It is your turn"), 0);
15842             return;
15843         }
15844         cps = &first;
15845         break;
15846       case MachinePlaysBlack:
15847         if (WhiteOnMove(forwardMostMove)) {
15848             DisplayError(_("It is your turn"), 0);
15849             return;
15850         }
15851         cps = &first;
15852         break;
15853       case TwoMachinesPlay:
15854         if (WhiteOnMove(forwardMostMove) ==
15855             (first.twoMachinesColor[0] == 'w')) {
15856             cps = &first;
15857         } else {
15858             cps = &second;
15859         }
15860         break;
15861       case BeginningOfGame:
15862       default:
15863         return;
15864     }
15865     SendToProgram("?\n", cps);
15866 }
15867
15868 void
15869 TruncateGameEvent ()
15870 {
15871     EditGameEvent();
15872     if (gameMode != EditGame) return;
15873     TruncateGame();
15874 }
15875
15876 void
15877 TruncateGame ()
15878 {
15879     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15880     if (forwardMostMove > currentMove) {
15881         if (gameInfo.resultDetails != NULL) {
15882             free(gameInfo.resultDetails);
15883             gameInfo.resultDetails = NULL;
15884             gameInfo.result = GameUnfinished;
15885         }
15886         forwardMostMove = currentMove;
15887         HistorySet(parseList, backwardMostMove, forwardMostMove,
15888                    currentMove-1);
15889     }
15890 }
15891
15892 void
15893 HintEvent ()
15894 {
15895     if (appData.noChessProgram) return;
15896     switch (gameMode) {
15897       case MachinePlaysWhite:
15898         if (WhiteOnMove(forwardMostMove)) {
15899             DisplayError(_("Wait until your turn."), 0);
15900             return;
15901         }
15902         break;
15903       case BeginningOfGame:
15904       case MachinePlaysBlack:
15905         if (!WhiteOnMove(forwardMostMove)) {
15906             DisplayError(_("Wait until your turn."), 0);
15907             return;
15908         }
15909         break;
15910       default:
15911         DisplayError(_("No hint available"), 0);
15912         return;
15913     }
15914     SendToProgram("hint\n", &first);
15915     hintRequested = TRUE;
15916 }
15917
15918 int
15919 SaveSelected (FILE *g, int dummy, char *dummy2)
15920 {
15921     ListGame * lg = (ListGame *) gameList.head;
15922     int nItem, cnt=0;
15923     FILE *f;
15924
15925     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15926         DisplayError(_("Game list not loaded or empty"), 0);
15927         return 0;
15928     }
15929
15930     creatingBook = TRUE; // suppresses stuff during load game
15931
15932     /* Get list size */
15933     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15934         if(lg->position >= 0) { // selected?
15935             LoadGame(f, nItem, "", TRUE);
15936             SaveGamePGN2(g); // leaves g open
15937             cnt++; DoEvents();
15938         }
15939         lg = (ListGame *) lg->node.succ;
15940     }
15941
15942     fclose(g);
15943     creatingBook = FALSE;
15944
15945     return cnt;
15946 }
15947
15948 void
15949 CreateBookEvent ()
15950 {
15951     ListGame * lg = (ListGame *) gameList.head;
15952     FILE *f, *g;
15953     int nItem;
15954     static int secondTime = FALSE;
15955
15956     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15957         DisplayError(_("Game list not loaded or empty"), 0);
15958         return;
15959     }
15960
15961     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15962         fclose(g);
15963         secondTime++;
15964         DisplayNote(_("Book file exists! Try again for overwrite."));
15965         return;
15966     }
15967
15968     creatingBook = TRUE;
15969     secondTime = FALSE;
15970
15971     /* Get list size */
15972     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15973         if(lg->position >= 0) {
15974             LoadGame(f, nItem, "", TRUE);
15975             AddGameToBook(TRUE);
15976             DoEvents();
15977         }
15978         lg = (ListGame *) lg->node.succ;
15979     }
15980
15981     creatingBook = FALSE;
15982     FlushBook();
15983 }
15984
15985 void
15986 BookEvent ()
15987 {
15988     if (appData.noChessProgram) return;
15989     switch (gameMode) {
15990       case MachinePlaysWhite:
15991         if (WhiteOnMove(forwardMostMove)) {
15992             DisplayError(_("Wait until your turn."), 0);
15993             return;
15994         }
15995         break;
15996       case BeginningOfGame:
15997       case MachinePlaysBlack:
15998         if (!WhiteOnMove(forwardMostMove)) {
15999             DisplayError(_("Wait until your turn."), 0);
16000             return;
16001         }
16002         break;
16003       case EditPosition:
16004         EditPositionDone(TRUE);
16005         break;
16006       case TwoMachinesPlay:
16007         return;
16008       default:
16009         break;
16010     }
16011     SendToProgram("bk\n", &first);
16012     bookOutput[0] = NULLCHAR;
16013     bookRequested = TRUE;
16014 }
16015
16016 void
16017 AboutGameEvent ()
16018 {
16019     char *tags = PGNTags(&gameInfo);
16020     TagsPopUp(tags, CmailMsg());
16021     free(tags);
16022 }
16023
16024 /* end button procedures */
16025
16026 void
16027 PrintPosition (FILE *fp, int move)
16028 {
16029     int i, j;
16030
16031     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16032         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16033             char c = PieceToChar(boards[move][i][j]);
16034             fputc(c == 'x' ? '.' : c, fp);
16035             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16036         }
16037     }
16038     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16039       fprintf(fp, "white to play\n");
16040     else
16041       fprintf(fp, "black to play\n");
16042 }
16043
16044 void
16045 PrintOpponents (FILE *fp)
16046 {
16047     if (gameInfo.white != NULL) {
16048         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16049     } else {
16050         fprintf(fp, "\n");
16051     }
16052 }
16053
16054 /* Find last component of program's own name, using some heuristics */
16055 void
16056 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16057 {
16058     char *p, *q, c;
16059     int local = (strcmp(host, "localhost") == 0);
16060     while (!local && (p = strchr(prog, ';')) != NULL) {
16061         p++;
16062         while (*p == ' ') p++;
16063         prog = p;
16064     }
16065     if (*prog == '"' || *prog == '\'') {
16066         q = strchr(prog + 1, *prog);
16067     } else {
16068         q = strchr(prog, ' ');
16069     }
16070     if (q == NULL) q = prog + strlen(prog);
16071     p = q;
16072     while (p >= prog && *p != '/' && *p != '\\') p--;
16073     p++;
16074     if(p == prog && *p == '"') p++;
16075     c = *q; *q = 0;
16076     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16077     memcpy(buf, p, q - p);
16078     buf[q - p] = NULLCHAR;
16079     if (!local) {
16080         strcat(buf, "@");
16081         strcat(buf, host);
16082     }
16083 }
16084
16085 char *
16086 TimeControlTagValue ()
16087 {
16088     char buf[MSG_SIZ];
16089     if (!appData.clockMode) {
16090       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16091     } else if (movesPerSession > 0) {
16092       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16093     } else if (timeIncrement == 0) {
16094       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16095     } else {
16096       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16097     }
16098     return StrSave(buf);
16099 }
16100
16101 void
16102 SetGameInfo ()
16103 {
16104     /* This routine is used only for certain modes */
16105     VariantClass v = gameInfo.variant;
16106     ChessMove r = GameUnfinished;
16107     char *p = NULL;
16108
16109     if(keepInfo) return;
16110
16111     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16112         r = gameInfo.result;
16113         p = gameInfo.resultDetails;
16114         gameInfo.resultDetails = NULL;
16115     }
16116     ClearGameInfo(&gameInfo);
16117     gameInfo.variant = v;
16118
16119     switch (gameMode) {
16120       case MachinePlaysWhite:
16121         gameInfo.event = StrSave( appData.pgnEventHeader );
16122         gameInfo.site = StrSave(HostName());
16123         gameInfo.date = PGNDate();
16124         gameInfo.round = StrSave("-");
16125         gameInfo.white = StrSave(first.tidy);
16126         gameInfo.black = StrSave(UserName());
16127         gameInfo.timeControl = TimeControlTagValue();
16128         break;
16129
16130       case MachinePlaysBlack:
16131         gameInfo.event = StrSave( appData.pgnEventHeader );
16132         gameInfo.site = StrSave(HostName());
16133         gameInfo.date = PGNDate();
16134         gameInfo.round = StrSave("-");
16135         gameInfo.white = StrSave(UserName());
16136         gameInfo.black = StrSave(first.tidy);
16137         gameInfo.timeControl = TimeControlTagValue();
16138         break;
16139
16140       case TwoMachinesPlay:
16141         gameInfo.event = StrSave( appData.pgnEventHeader );
16142         gameInfo.site = StrSave(HostName());
16143         gameInfo.date = PGNDate();
16144         if (roundNr > 0) {
16145             char buf[MSG_SIZ];
16146             snprintf(buf, MSG_SIZ, "%d", roundNr);
16147             gameInfo.round = StrSave(buf);
16148         } else {
16149             gameInfo.round = StrSave("-");
16150         }
16151         if (first.twoMachinesColor[0] == 'w') {
16152             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16153             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16154         } else {
16155             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16156             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16157         }
16158         gameInfo.timeControl = TimeControlTagValue();
16159         break;
16160
16161       case EditGame:
16162         gameInfo.event = StrSave("Edited game");
16163         gameInfo.site = StrSave(HostName());
16164         gameInfo.date = PGNDate();
16165         gameInfo.round = StrSave("-");
16166         gameInfo.white = StrSave("-");
16167         gameInfo.black = StrSave("-");
16168         gameInfo.result = r;
16169         gameInfo.resultDetails = p;
16170         break;
16171
16172       case EditPosition:
16173         gameInfo.event = StrSave("Edited position");
16174         gameInfo.site = StrSave(HostName());
16175         gameInfo.date = PGNDate();
16176         gameInfo.round = StrSave("-");
16177         gameInfo.white = StrSave("-");
16178         gameInfo.black = StrSave("-");
16179         break;
16180
16181       case IcsPlayingWhite:
16182       case IcsPlayingBlack:
16183       case IcsObserving:
16184       case IcsExamining:
16185         break;
16186
16187       case PlayFromGameFile:
16188         gameInfo.event = StrSave("Game from non-PGN file");
16189         gameInfo.site = StrSave(HostName());
16190         gameInfo.date = PGNDate();
16191         gameInfo.round = StrSave("-");
16192         gameInfo.white = StrSave("?");
16193         gameInfo.black = StrSave("?");
16194         break;
16195
16196       default:
16197         break;
16198     }
16199 }
16200
16201 void
16202 ReplaceComment (int index, char *text)
16203 {
16204     int len;
16205     char *p;
16206     float score;
16207
16208     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16209        pvInfoList[index-1].depth == len &&
16210        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16211        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16212     while (*text == '\n') text++;
16213     len = strlen(text);
16214     while (len > 0 && text[len - 1] == '\n') len--;
16215
16216     if (commentList[index] != NULL)
16217       free(commentList[index]);
16218
16219     if (len == 0) {
16220         commentList[index] = NULL;
16221         return;
16222     }
16223   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16224       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16225       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16226     commentList[index] = (char *) malloc(len + 2);
16227     strncpy(commentList[index], text, len);
16228     commentList[index][len] = '\n';
16229     commentList[index][len + 1] = NULLCHAR;
16230   } else {
16231     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16232     char *p;
16233     commentList[index] = (char *) malloc(len + 7);
16234     safeStrCpy(commentList[index], "{\n", 3);
16235     safeStrCpy(commentList[index]+2, text, len+1);
16236     commentList[index][len+2] = NULLCHAR;
16237     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16238     strcat(commentList[index], "\n}\n");
16239   }
16240 }
16241
16242 void
16243 CrushCRs (char *text)
16244 {
16245   char *p = text;
16246   char *q = text;
16247   char ch;
16248
16249   do {
16250     ch = *p++;
16251     if (ch == '\r') continue;
16252     *q++ = ch;
16253   } while (ch != '\0');
16254 }
16255
16256 void
16257 AppendComment (int index, char *text, Boolean addBraces)
16258 /* addBraces  tells if we should add {} */
16259 {
16260     int oldlen, len;
16261     char *old;
16262
16263 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16264     if(addBraces == 3) addBraces = 0; else // force appending literally
16265     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16266
16267     CrushCRs(text);
16268     while (*text == '\n') text++;
16269     len = strlen(text);
16270     while (len > 0 && text[len - 1] == '\n') len--;
16271     text[len] = NULLCHAR;
16272
16273     if (len == 0) return;
16274
16275     if (commentList[index] != NULL) {
16276       Boolean addClosingBrace = addBraces;
16277         old = commentList[index];
16278         oldlen = strlen(old);
16279         while(commentList[index][oldlen-1] ==  '\n')
16280           commentList[index][--oldlen] = NULLCHAR;
16281         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16282         safeStrCpy(commentList[index], old, oldlen + len + 6);
16283         free(old);
16284         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16285         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16286           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16287           while (*text == '\n') { text++; len--; }
16288           commentList[index][--oldlen] = NULLCHAR;
16289       }
16290         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16291         else          strcat(commentList[index], "\n");
16292         strcat(commentList[index], text);
16293         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16294         else          strcat(commentList[index], "\n");
16295     } else {
16296         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16297         if(addBraces)
16298           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16299         else commentList[index][0] = NULLCHAR;
16300         strcat(commentList[index], text);
16301         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16302         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16303     }
16304 }
16305
16306 static char *
16307 FindStr (char * text, char * sub_text)
16308 {
16309     char * result = strstr( text, sub_text );
16310
16311     if( result != NULL ) {
16312         result += strlen( sub_text );
16313     }
16314
16315     return result;
16316 }
16317
16318 /* [AS] Try to extract PV info from PGN comment */
16319 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16320 char *
16321 GetInfoFromComment (int index, char * text)
16322 {
16323     char * sep = text, *p;
16324
16325     if( text != NULL && index > 0 ) {
16326         int score = 0;
16327         int depth = 0;
16328         int time = -1, sec = 0, deci;
16329         char * s_eval = FindStr( text, "[%eval " );
16330         char * s_emt = FindStr( text, "[%emt " );
16331 #if 0
16332         if( s_eval != NULL || s_emt != NULL ) {
16333 #else
16334         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16335 #endif
16336             /* New style */
16337             char delim;
16338
16339             if( s_eval != NULL ) {
16340                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16341                     return text;
16342                 }
16343
16344                 if( delim != ']' ) {
16345                     return text;
16346                 }
16347             }
16348
16349             if( s_emt != NULL ) {
16350             }
16351                 return text;
16352         }
16353         else {
16354             /* We expect something like: [+|-]nnn.nn/dd */
16355             int score_lo = 0;
16356
16357             if(*text != '{') return text; // [HGM] braces: must be normal comment
16358
16359             sep = strchr( text, '/' );
16360             if( sep == NULL || sep < (text+4) ) {
16361                 return text;
16362             }
16363
16364             p = text;
16365             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16366             if(p[1] == '(') { // comment starts with PV
16367                p = strchr(p, ')'); // locate end of PV
16368                if(p == NULL || sep < p+5) return text;
16369                // at this point we have something like "{(.*) +0.23/6 ..."
16370                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16371                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16372                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16373             }
16374             time = -1; sec = -1; deci = -1;
16375             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16376                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16377                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16378                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16379                 return text;
16380             }
16381
16382             if( score_lo < 0 || score_lo >= 100 ) {
16383                 return text;
16384             }
16385
16386             if(sec >= 0) time = 600*time + 10*sec; else
16387             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16388
16389             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16390
16391             /* [HGM] PV time: now locate end of PV info */
16392             while( *++sep >= '0' && *sep <= '9'); // strip depth
16393             if(time >= 0)
16394             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16395             if(sec >= 0)
16396             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16397             if(deci >= 0)
16398             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16399             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16400         }
16401
16402         if( depth <= 0 ) {
16403             return text;
16404         }
16405
16406         if( time < 0 ) {
16407             time = -1;
16408         }
16409
16410         pvInfoList[index-1].depth = depth;
16411         pvInfoList[index-1].score = score;
16412         pvInfoList[index-1].time  = 10*time; // centi-sec
16413         if(*sep == '}') *sep = 0; else *--sep = '{';
16414         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16415     }
16416     return sep;
16417 }
16418
16419 void
16420 SendToProgram (char *message, ChessProgramState *cps)
16421 {
16422     int count, outCount, error;
16423     char buf[MSG_SIZ];
16424
16425     if (cps->pr == NoProc) return;
16426     Attention(cps);
16427
16428     if (appData.debugMode) {
16429         TimeMark now;
16430         GetTimeMark(&now);
16431         fprintf(debugFP, "%ld >%-6s: %s",
16432                 SubtractTimeMarks(&now, &programStartTime),
16433                 cps->which, message);
16434         if(serverFP)
16435             fprintf(serverFP, "%ld >%-6s: %s",
16436                 SubtractTimeMarks(&now, &programStartTime),
16437                 cps->which, message), fflush(serverFP);
16438     }
16439
16440     count = strlen(message);
16441     outCount = OutputToProcess(cps->pr, message, count, &error);
16442     if (outCount < count && !exiting
16443                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16444       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16445       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16446         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16447             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16448                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16449                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16450                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16451             } else {
16452                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16453                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16454                 gameInfo.result = res;
16455             }
16456             gameInfo.resultDetails = StrSave(buf);
16457         }
16458         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16459         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16460     }
16461 }
16462
16463 void
16464 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16465 {
16466     char *end_str;
16467     char buf[MSG_SIZ];
16468     ChessProgramState *cps = (ChessProgramState *)closure;
16469
16470     if (isr != cps->isr) return; /* Killed intentionally */
16471     if (count <= 0) {
16472         if (count == 0) {
16473             RemoveInputSource(cps->isr);
16474             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16475                     _(cps->which), cps->program);
16476             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16477             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16478                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16479                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16480                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16481                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16482                 } else {
16483                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16484                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16485                     gameInfo.result = res;
16486                 }
16487                 gameInfo.resultDetails = StrSave(buf);
16488             }
16489             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16490             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16491         } else {
16492             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16493                     _(cps->which), cps->program);
16494             RemoveInputSource(cps->isr);
16495
16496             /* [AS] Program is misbehaving badly... kill it */
16497             if( count == -2 ) {
16498                 DestroyChildProcess( cps->pr, 9 );
16499                 cps->pr = NoProc;
16500             }
16501
16502             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16503         }
16504         return;
16505     }
16506
16507     if ((end_str = strchr(message, '\r')) != NULL)
16508       *end_str = NULLCHAR;
16509     if ((end_str = strchr(message, '\n')) != NULL)
16510       *end_str = NULLCHAR;
16511
16512     if (appData.debugMode) {
16513         TimeMark now; int print = 1;
16514         char *quote = ""; char c; int i;
16515
16516         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16517                 char start = message[0];
16518                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16519                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16520                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16521                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16522                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16523                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16524                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16525                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16526                    sscanf(message, "hint: %c", &c)!=1 &&
16527                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16528                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16529                     print = (appData.engineComments >= 2);
16530                 }
16531                 message[0] = start; // restore original message
16532         }
16533         if(print) {
16534                 GetTimeMark(&now);
16535                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16536                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16537                         quote,
16538                         message);
16539                 if(serverFP)
16540                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16541                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16542                         quote,
16543                         message), fflush(serverFP);
16544         }
16545     }
16546
16547     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16548     if (appData.icsEngineAnalyze) {
16549         if (strstr(message, "whisper") != NULL ||
16550              strstr(message, "kibitz") != NULL ||
16551             strstr(message, "tellics") != NULL) return;
16552     }
16553
16554     HandleMachineMove(message, cps);
16555 }
16556
16557
16558 void
16559 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16560 {
16561     char buf[MSG_SIZ];
16562     int seconds;
16563
16564     if( timeControl_2 > 0 ) {
16565         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16566             tc = timeControl_2;
16567         }
16568     }
16569     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16570     inc /= cps->timeOdds;
16571     st  /= cps->timeOdds;
16572
16573     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16574
16575     if (st > 0) {
16576       /* Set exact time per move, normally using st command */
16577       if (cps->stKludge) {
16578         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16579         seconds = st % 60;
16580         if (seconds == 0) {
16581           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16582         } else {
16583           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16584         }
16585       } else {
16586         snprintf(buf, MSG_SIZ, "st %d\n", st);
16587       }
16588     } else {
16589       /* Set conventional or incremental time control, using level command */
16590       if (seconds == 0) {
16591         /* Note old gnuchess bug -- minutes:seconds used to not work.
16592            Fixed in later versions, but still avoid :seconds
16593            when seconds is 0. */
16594         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16595       } else {
16596         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16597                  seconds, inc/1000.);
16598       }
16599     }
16600     SendToProgram(buf, cps);
16601
16602     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16603     /* Orthogonally, limit search to given depth */
16604     if (sd > 0) {
16605       if (cps->sdKludge) {
16606         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16607       } else {
16608         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16609       }
16610       SendToProgram(buf, cps);
16611     }
16612
16613     if(cps->nps >= 0) { /* [HGM] nps */
16614         if(cps->supportsNPS == FALSE)
16615           cps->nps = -1; // don't use if engine explicitly says not supported!
16616         else {
16617           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16618           SendToProgram(buf, cps);
16619         }
16620     }
16621 }
16622
16623 ChessProgramState *
16624 WhitePlayer ()
16625 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16626 {
16627     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16628        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16629         return &second;
16630     return &first;
16631 }
16632
16633 void
16634 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16635 {
16636     char message[MSG_SIZ];
16637     long time, otime;
16638
16639     /* Note: this routine must be called when the clocks are stopped
16640        or when they have *just* been set or switched; otherwise
16641        it will be off by the time since the current tick started.
16642     */
16643     if (machineWhite) {
16644         time = whiteTimeRemaining / 10;
16645         otime = blackTimeRemaining / 10;
16646     } else {
16647         time = blackTimeRemaining / 10;
16648         otime = whiteTimeRemaining / 10;
16649     }
16650     /* [HGM] translate opponent's time by time-odds factor */
16651     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16652
16653     if (time <= 0) time = 1;
16654     if (otime <= 0) otime = 1;
16655
16656     snprintf(message, MSG_SIZ, "time %ld\n", time);
16657     SendToProgram(message, cps);
16658
16659     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16660     SendToProgram(message, cps);
16661 }
16662
16663 char *
16664 EngineDefinedVariant (ChessProgramState *cps, int n)
16665 {   // return name of n-th unknown variant that engine supports
16666     static char buf[MSG_SIZ];
16667     char *p, *s = cps->variants;
16668     if(!s) return NULL;
16669     do { // parse string from variants feature
16670       VariantClass v;
16671         p = strchr(s, ',');
16672         if(p) *p = NULLCHAR;
16673       v = StringToVariant(s);
16674       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16675         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16676             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16677                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16678                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16679                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16680             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16681         }
16682         if(p) *p++ = ',';
16683         if(n < 0) return buf;
16684     } while(s = p);
16685     return NULL;
16686 }
16687
16688 int
16689 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16690 {
16691   char buf[MSG_SIZ];
16692   int len = strlen(name);
16693   int val;
16694
16695   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16696     (*p) += len + 1;
16697     sscanf(*p, "%d", &val);
16698     *loc = (val != 0);
16699     while (**p && **p != ' ')
16700       (*p)++;
16701     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16702     SendToProgram(buf, cps);
16703     return TRUE;
16704   }
16705   return FALSE;
16706 }
16707
16708 int
16709 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16710 {
16711   char buf[MSG_SIZ];
16712   int len = strlen(name);
16713   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16714     (*p) += len + 1;
16715     sscanf(*p, "%d", loc);
16716     while (**p && **p != ' ') (*p)++;
16717     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16718     SendToProgram(buf, cps);
16719     return TRUE;
16720   }
16721   return FALSE;
16722 }
16723
16724 int
16725 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16726 {
16727   char buf[MSG_SIZ];
16728   int len = strlen(name);
16729   if (strncmp((*p), name, len) == 0
16730       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16731     (*p) += len + 2;
16732     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16733     sscanf(*p, "%[^\"]", *loc);
16734     while (**p && **p != '\"') (*p)++;
16735     if (**p == '\"') (*p)++;
16736     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16737     SendToProgram(buf, cps);
16738     return TRUE;
16739   }
16740   return FALSE;
16741 }
16742
16743 int
16744 ParseOption (Option *opt, ChessProgramState *cps)
16745 // [HGM] options: process the string that defines an engine option, and determine
16746 // name, type, default value, and allowed value range
16747 {
16748         char *p, *q, buf[MSG_SIZ];
16749         int n, min = (-1)<<31, max = 1<<31, def;
16750
16751         if(p = strstr(opt->name, " -spin ")) {
16752             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16753             if(max < min) max = min; // enforce consistency
16754             if(def < min) def = min;
16755             if(def > max) def = max;
16756             opt->value = def;
16757             opt->min = min;
16758             opt->max = max;
16759             opt->type = Spin;
16760         } else if((p = strstr(opt->name, " -slider "))) {
16761             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16762             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16763             if(max < min) max = min; // enforce consistency
16764             if(def < min) def = min;
16765             if(def > max) def = max;
16766             opt->value = def;
16767             opt->min = min;
16768             opt->max = max;
16769             opt->type = Spin; // Slider;
16770         } else if((p = strstr(opt->name, " -string "))) {
16771             opt->textValue = p+9;
16772             opt->type = TextBox;
16773         } else if((p = strstr(opt->name, " -file "))) {
16774             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16775             opt->textValue = p+7;
16776             opt->type = FileName; // FileName;
16777         } else if((p = strstr(opt->name, " -path "))) {
16778             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16779             opt->textValue = p+7;
16780             opt->type = PathName; // PathName;
16781         } else if(p = strstr(opt->name, " -check ")) {
16782             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16783             opt->value = (def != 0);
16784             opt->type = CheckBox;
16785         } else if(p = strstr(opt->name, " -combo ")) {
16786             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16787             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16788             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16789             opt->value = n = 0;
16790             while(q = StrStr(q, " /// ")) {
16791                 n++; *q = 0;    // count choices, and null-terminate each of them
16792                 q += 5;
16793                 if(*q == '*') { // remember default, which is marked with * prefix
16794                     q++;
16795                     opt->value = n;
16796                 }
16797                 cps->comboList[cps->comboCnt++] = q;
16798             }
16799             cps->comboList[cps->comboCnt++] = NULL;
16800             opt->max = n + 1;
16801             opt->type = ComboBox;
16802         } else if(p = strstr(opt->name, " -button")) {
16803             opt->type = Button;
16804         } else if(p = strstr(opt->name, " -save")) {
16805             opt->type = SaveButton;
16806         } else return FALSE;
16807         *p = 0; // terminate option name
16808         // now look if the command-line options define a setting for this engine option.
16809         if(cps->optionSettings && cps->optionSettings[0])
16810             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16811         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16812           snprintf(buf, MSG_SIZ, "option %s", p);
16813                 if(p = strstr(buf, ",")) *p = 0;
16814                 if(q = strchr(buf, '=')) switch(opt->type) {
16815                     case ComboBox:
16816                         for(n=0; n<opt->max; n++)
16817                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16818                         break;
16819                     case TextBox:
16820                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16821                         break;
16822                     case Spin:
16823                     case CheckBox:
16824                         opt->value = atoi(q+1);
16825                     default:
16826                         break;
16827                 }
16828                 strcat(buf, "\n");
16829                 SendToProgram(buf, cps);
16830         }
16831         return TRUE;
16832 }
16833
16834 void
16835 FeatureDone (ChessProgramState *cps, int val)
16836 {
16837   DelayedEventCallback cb = GetDelayedEvent();
16838   if ((cb == InitBackEnd3 && cps == &first) ||
16839       (cb == SettingsMenuIfReady && cps == &second) ||
16840       (cb == LoadEngine) ||
16841       (cb == TwoMachinesEventIfReady)) {
16842     CancelDelayedEvent();
16843     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16844   }
16845   cps->initDone = val;
16846   if(val) cps->reload = FALSE;
16847 }
16848
16849 /* Parse feature command from engine */
16850 void
16851 ParseFeatures (char *args, ChessProgramState *cps)
16852 {
16853   char *p = args;
16854   char *q = NULL;
16855   int val;
16856   char buf[MSG_SIZ];
16857
16858   for (;;) {
16859     while (*p == ' ') p++;
16860     if (*p == NULLCHAR) return;
16861
16862     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16863     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16864     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16865     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16866     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16867     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16868     if (BoolFeature(&p, "reuse", &val, cps)) {
16869       /* Engine can disable reuse, but can't enable it if user said no */
16870       if (!val) cps->reuse = FALSE;
16871       continue;
16872     }
16873     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16874     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16875       if (gameMode == TwoMachinesPlay) {
16876         DisplayTwoMachinesTitle();
16877       } else {
16878         DisplayTitle("");
16879       }
16880       continue;
16881     }
16882     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16883     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16884     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16885     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16886     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16887     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16888     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16889     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16890     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16891     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16892     if (IntFeature(&p, "done", &val, cps)) {
16893       FeatureDone(cps, val);
16894       continue;
16895     }
16896     /* Added by Tord: */
16897     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16898     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16899     /* End of additions by Tord */
16900
16901     /* [HGM] added features: */
16902     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16903     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16904     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16905     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16906     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16907     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16908     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16909     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16910         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16911         FREE(cps->option[cps->nrOptions].name);
16912         cps->option[cps->nrOptions].name = q; q = NULL;
16913         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16914           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16915             SendToProgram(buf, cps);
16916             continue;
16917         }
16918         if(cps->nrOptions >= MAX_OPTIONS) {
16919             cps->nrOptions--;
16920             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16921             DisplayError(buf, 0);
16922         }
16923         continue;
16924     }
16925     /* End of additions by HGM */
16926
16927     /* unknown feature: complain and skip */
16928     q = p;
16929     while (*q && *q != '=') q++;
16930     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16931     SendToProgram(buf, cps);
16932     p = q;
16933     if (*p == '=') {
16934       p++;
16935       if (*p == '\"') {
16936         p++;
16937         while (*p && *p != '\"') p++;
16938         if (*p == '\"') p++;
16939       } else {
16940         while (*p && *p != ' ') p++;
16941       }
16942     }
16943   }
16944
16945 }
16946
16947 void
16948 PeriodicUpdatesEvent (int newState)
16949 {
16950     if (newState == appData.periodicUpdates)
16951       return;
16952
16953     appData.periodicUpdates=newState;
16954
16955     /* Display type changes, so update it now */
16956 //    DisplayAnalysis();
16957
16958     /* Get the ball rolling again... */
16959     if (newState) {
16960         AnalysisPeriodicEvent(1);
16961         StartAnalysisClock();
16962     }
16963 }
16964
16965 void
16966 PonderNextMoveEvent (int newState)
16967 {
16968     if (newState == appData.ponderNextMove) return;
16969     if (gameMode == EditPosition) EditPositionDone(TRUE);
16970     if (newState) {
16971         SendToProgram("hard\n", &first);
16972         if (gameMode == TwoMachinesPlay) {
16973             SendToProgram("hard\n", &second);
16974         }
16975     } else {
16976         SendToProgram("easy\n", &first);
16977         thinkOutput[0] = NULLCHAR;
16978         if (gameMode == TwoMachinesPlay) {
16979             SendToProgram("easy\n", &second);
16980         }
16981     }
16982     appData.ponderNextMove = newState;
16983 }
16984
16985 void
16986 NewSettingEvent (int option, int *feature, char *command, int value)
16987 {
16988     char buf[MSG_SIZ];
16989
16990     if (gameMode == EditPosition) EditPositionDone(TRUE);
16991     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16992     if(feature == NULL || *feature) SendToProgram(buf, &first);
16993     if (gameMode == TwoMachinesPlay) {
16994         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16995     }
16996 }
16997
16998 void
16999 ShowThinkingEvent ()
17000 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17001 {
17002     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17003     int newState = appData.showThinking
17004         // [HGM] thinking: other features now need thinking output as well
17005         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17006
17007     if (oldState == newState) return;
17008     oldState = newState;
17009     if (gameMode == EditPosition) EditPositionDone(TRUE);
17010     if (oldState) {
17011         SendToProgram("post\n", &first);
17012         if (gameMode == TwoMachinesPlay) {
17013             SendToProgram("post\n", &second);
17014         }
17015     } else {
17016         SendToProgram("nopost\n", &first);
17017         thinkOutput[0] = NULLCHAR;
17018         if (gameMode == TwoMachinesPlay) {
17019             SendToProgram("nopost\n", &second);
17020         }
17021     }
17022 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17023 }
17024
17025 void
17026 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17027 {
17028   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17029   if (pr == NoProc) return;
17030   AskQuestion(title, question, replyPrefix, pr);
17031 }
17032
17033 void
17034 TypeInEvent (char firstChar)
17035 {
17036     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17037         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17038         gameMode == AnalyzeMode || gameMode == EditGame ||
17039         gameMode == EditPosition || gameMode == IcsExamining ||
17040         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17041         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17042                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17043                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17044         gameMode == Training) PopUpMoveDialog(firstChar);
17045 }
17046
17047 void
17048 TypeInDoneEvent (char *move)
17049 {
17050         Board board;
17051         int n, fromX, fromY, toX, toY;
17052         char promoChar;
17053         ChessMove moveType;
17054
17055         // [HGM] FENedit
17056         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17057                 EditPositionPasteFEN(move);
17058                 return;
17059         }
17060         // [HGM] movenum: allow move number to be typed in any mode
17061         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17062           ToNrEvent(2*n-1);
17063           return;
17064         }
17065         // undocumented kludge: allow command-line option to be typed in!
17066         // (potentially fatal, and does not implement the effect of the option.)
17067         // should only be used for options that are values on which future decisions will be made,
17068         // and definitely not on options that would be used during initialization.
17069         if(strstr(move, "!!! -") == move) {
17070             ParseArgsFromString(move+4);
17071             return;
17072         }
17073
17074       if (gameMode != EditGame && currentMove != forwardMostMove &&
17075         gameMode != Training) {
17076         DisplayMoveError(_("Displayed move is not current"));
17077       } else {
17078         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17079           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17080         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17081         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17082           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17083           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17084         } else {
17085           DisplayMoveError(_("Could not parse move"));
17086         }
17087       }
17088 }
17089
17090 void
17091 DisplayMove (int moveNumber)
17092 {
17093     char message[MSG_SIZ];
17094     char res[MSG_SIZ];
17095     char cpThinkOutput[MSG_SIZ];
17096
17097     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17098
17099     if (moveNumber == forwardMostMove - 1 ||
17100         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17101
17102         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17103
17104         if (strchr(cpThinkOutput, '\n')) {
17105             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17106         }
17107     } else {
17108         *cpThinkOutput = NULLCHAR;
17109     }
17110
17111     /* [AS] Hide thinking from human user */
17112     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17113         *cpThinkOutput = NULLCHAR;
17114         if( thinkOutput[0] != NULLCHAR ) {
17115             int i;
17116
17117             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17118                 cpThinkOutput[i] = '.';
17119             }
17120             cpThinkOutput[i] = NULLCHAR;
17121             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17122         }
17123     }
17124
17125     if (moveNumber == forwardMostMove - 1 &&
17126         gameInfo.resultDetails != NULL) {
17127         if (gameInfo.resultDetails[0] == NULLCHAR) {
17128           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17129         } else {
17130           snprintf(res, MSG_SIZ, " {%s} %s",
17131                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17132         }
17133     } else {
17134         res[0] = NULLCHAR;
17135     }
17136
17137     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17138         DisplayMessage(res, cpThinkOutput);
17139     } else {
17140       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17141                 WhiteOnMove(moveNumber) ? " " : ".. ",
17142                 parseList[moveNumber], res);
17143         DisplayMessage(message, cpThinkOutput);
17144     }
17145 }
17146
17147 void
17148 DisplayComment (int moveNumber, char *text)
17149 {
17150     char title[MSG_SIZ];
17151
17152     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17153       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17154     } else {
17155       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17156               WhiteOnMove(moveNumber) ? " " : ".. ",
17157               parseList[moveNumber]);
17158     }
17159     if (text != NULL && (appData.autoDisplayComment || commentUp))
17160         CommentPopUp(title, text);
17161 }
17162
17163 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17164  * might be busy thinking or pondering.  It can be omitted if your
17165  * gnuchess is configured to stop thinking immediately on any user
17166  * input.  However, that gnuchess feature depends on the FIONREAD
17167  * ioctl, which does not work properly on some flavors of Unix.
17168  */
17169 void
17170 Attention (ChessProgramState *cps)
17171 {
17172 #if ATTENTION
17173     if (!cps->useSigint) return;
17174     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17175     switch (gameMode) {
17176       case MachinePlaysWhite:
17177       case MachinePlaysBlack:
17178       case TwoMachinesPlay:
17179       case IcsPlayingWhite:
17180       case IcsPlayingBlack:
17181       case AnalyzeMode:
17182       case AnalyzeFile:
17183         /* Skip if we know it isn't thinking */
17184         if (!cps->maybeThinking) return;
17185         if (appData.debugMode)
17186           fprintf(debugFP, "Interrupting %s\n", cps->which);
17187         InterruptChildProcess(cps->pr);
17188         cps->maybeThinking = FALSE;
17189         break;
17190       default:
17191         break;
17192     }
17193 #endif /*ATTENTION*/
17194 }
17195
17196 int
17197 CheckFlags ()
17198 {
17199     if (whiteTimeRemaining <= 0) {
17200         if (!whiteFlag) {
17201             whiteFlag = TRUE;
17202             if (appData.icsActive) {
17203                 if (appData.autoCallFlag &&
17204                     gameMode == IcsPlayingBlack && !blackFlag) {
17205                   SendToICS(ics_prefix);
17206                   SendToICS("flag\n");
17207                 }
17208             } else {
17209                 if (blackFlag) {
17210                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17211                 } else {
17212                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17213                     if (appData.autoCallFlag) {
17214                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17215                         return TRUE;
17216                     }
17217                 }
17218             }
17219         }
17220     }
17221     if (blackTimeRemaining <= 0) {
17222         if (!blackFlag) {
17223             blackFlag = TRUE;
17224             if (appData.icsActive) {
17225                 if (appData.autoCallFlag &&
17226                     gameMode == IcsPlayingWhite && !whiteFlag) {
17227                   SendToICS(ics_prefix);
17228                   SendToICS("flag\n");
17229                 }
17230             } else {
17231                 if (whiteFlag) {
17232                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17233                 } else {
17234                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17235                     if (appData.autoCallFlag) {
17236                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17237                         return TRUE;
17238                     }
17239                 }
17240             }
17241         }
17242     }
17243     return FALSE;
17244 }
17245
17246 void
17247 CheckTimeControl ()
17248 {
17249     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17250         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17251
17252     /*
17253      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17254      */
17255     if ( !WhiteOnMove(forwardMostMove) ) {
17256         /* White made time control */
17257         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17258         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17259         /* [HGM] time odds: correct new time quota for time odds! */
17260                                             / WhitePlayer()->timeOdds;
17261         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17262     } else {
17263         lastBlack -= blackTimeRemaining;
17264         /* Black made time control */
17265         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17266                                             / WhitePlayer()->other->timeOdds;
17267         lastWhite = whiteTimeRemaining;
17268     }
17269 }
17270
17271 void
17272 DisplayBothClocks ()
17273 {
17274     int wom = gameMode == EditPosition ?
17275       !blackPlaysFirst : WhiteOnMove(currentMove);
17276     DisplayWhiteClock(whiteTimeRemaining, wom);
17277     DisplayBlackClock(blackTimeRemaining, !wom);
17278 }
17279
17280
17281 /* Timekeeping seems to be a portability nightmare.  I think everyone
17282    has ftime(), but I'm really not sure, so I'm including some ifdefs
17283    to use other calls if you don't.  Clocks will be less accurate if
17284    you have neither ftime nor gettimeofday.
17285 */
17286
17287 /* VS 2008 requires the #include outside of the function */
17288 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17289 #include <sys/timeb.h>
17290 #endif
17291
17292 /* Get the current time as a TimeMark */
17293 void
17294 GetTimeMark (TimeMark *tm)
17295 {
17296 #if HAVE_GETTIMEOFDAY
17297
17298     struct timeval timeVal;
17299     struct timezone timeZone;
17300
17301     gettimeofday(&timeVal, &timeZone);
17302     tm->sec = (long) timeVal.tv_sec;
17303     tm->ms = (int) (timeVal.tv_usec / 1000L);
17304
17305 #else /*!HAVE_GETTIMEOFDAY*/
17306 #if HAVE_FTIME
17307
17308 // include <sys/timeb.h> / moved to just above start of function
17309     struct timeb timeB;
17310
17311     ftime(&timeB);
17312     tm->sec = (long) timeB.time;
17313     tm->ms = (int) timeB.millitm;
17314
17315 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17316     tm->sec = (long) time(NULL);
17317     tm->ms = 0;
17318 #endif
17319 #endif
17320 }
17321
17322 /* Return the difference in milliseconds between two
17323    time marks.  We assume the difference will fit in a long!
17324 */
17325 long
17326 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17327 {
17328     return 1000L*(tm2->sec - tm1->sec) +
17329            (long) (tm2->ms - tm1->ms);
17330 }
17331
17332
17333 /*
17334  * Code to manage the game clocks.
17335  *
17336  * In tournament play, black starts the clock and then white makes a move.
17337  * We give the human user a slight advantage if he is playing white---the
17338  * clocks don't run until he makes his first move, so it takes zero time.
17339  * Also, we don't account for network lag, so we could get out of sync
17340  * with GNU Chess's clock -- but then, referees are always right.
17341  */
17342
17343 static TimeMark tickStartTM;
17344 static long intendedTickLength;
17345
17346 long
17347 NextTickLength (long timeRemaining)
17348 {
17349     long nominalTickLength, nextTickLength;
17350
17351     if (timeRemaining > 0L && timeRemaining <= 10000L)
17352       nominalTickLength = 100L;
17353     else
17354       nominalTickLength = 1000L;
17355     nextTickLength = timeRemaining % nominalTickLength;
17356     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17357
17358     return nextTickLength;
17359 }
17360
17361 /* Adjust clock one minute up or down */
17362 void
17363 AdjustClock (Boolean which, int dir)
17364 {
17365     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17366     if(which) blackTimeRemaining += 60000*dir;
17367     else      whiteTimeRemaining += 60000*dir;
17368     DisplayBothClocks();
17369     adjustedClock = TRUE;
17370 }
17371
17372 /* Stop clocks and reset to a fresh time control */
17373 void
17374 ResetClocks ()
17375 {
17376     (void) StopClockTimer();
17377     if (appData.icsActive) {
17378         whiteTimeRemaining = blackTimeRemaining = 0;
17379     } else if (searchTime) {
17380         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17381         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17382     } else { /* [HGM] correct new time quote for time odds */
17383         whiteTC = blackTC = fullTimeControlString;
17384         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17385         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17386     }
17387     if (whiteFlag || blackFlag) {
17388         DisplayTitle("");
17389         whiteFlag = blackFlag = FALSE;
17390     }
17391     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17392     DisplayBothClocks();
17393     adjustedClock = FALSE;
17394 }
17395
17396 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17397
17398 /* Decrement running clock by amount of time that has passed */
17399 void
17400 DecrementClocks ()
17401 {
17402     long timeRemaining;
17403     long lastTickLength, fudge;
17404     TimeMark now;
17405
17406     if (!appData.clockMode) return;
17407     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17408
17409     GetTimeMark(&now);
17410
17411     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17412
17413     /* Fudge if we woke up a little too soon */
17414     fudge = intendedTickLength - lastTickLength;
17415     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17416
17417     if (WhiteOnMove(forwardMostMove)) {
17418         if(whiteNPS >= 0) lastTickLength = 0;
17419         timeRemaining = whiteTimeRemaining -= lastTickLength;
17420         if(timeRemaining < 0 && !appData.icsActive) {
17421             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17422             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17423                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17424                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17425             }
17426         }
17427         DisplayWhiteClock(whiteTimeRemaining - fudge,
17428                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17429     } else {
17430         if(blackNPS >= 0) lastTickLength = 0;
17431         timeRemaining = blackTimeRemaining -= lastTickLength;
17432         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17433             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17434             if(suddenDeath) {
17435                 blackStartMove = forwardMostMove;
17436                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17437             }
17438         }
17439         DisplayBlackClock(blackTimeRemaining - fudge,
17440                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17441     }
17442     if (CheckFlags()) return;
17443
17444     if(twoBoards) { // count down secondary board's clocks as well
17445         activePartnerTime -= lastTickLength;
17446         partnerUp = 1;
17447         if(activePartner == 'W')
17448             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17449         else
17450             DisplayBlackClock(activePartnerTime, TRUE);
17451         partnerUp = 0;
17452     }
17453
17454     tickStartTM = now;
17455     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17456     StartClockTimer(intendedTickLength);
17457
17458     /* if the time remaining has fallen below the alarm threshold, sound the
17459      * alarm. if the alarm has sounded and (due to a takeback or time control
17460      * with increment) the time remaining has increased to a level above the
17461      * threshold, reset the alarm so it can sound again.
17462      */
17463
17464     if (appData.icsActive && appData.icsAlarm) {
17465
17466         /* make sure we are dealing with the user's clock */
17467         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17468                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17469            )) return;
17470
17471         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17472             alarmSounded = FALSE;
17473         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17474             PlayAlarmSound();
17475             alarmSounded = TRUE;
17476         }
17477     }
17478 }
17479
17480
17481 /* A player has just moved, so stop the previously running
17482    clock and (if in clock mode) start the other one.
17483    We redisplay both clocks in case we're in ICS mode, because
17484    ICS gives us an update to both clocks after every move.
17485    Note that this routine is called *after* forwardMostMove
17486    is updated, so the last fractional tick must be subtracted
17487    from the color that is *not* on move now.
17488 */
17489 void
17490 SwitchClocks (int newMoveNr)
17491 {
17492     long lastTickLength;
17493     TimeMark now;
17494     int flagged = FALSE;
17495
17496     GetTimeMark(&now);
17497
17498     if (StopClockTimer() && appData.clockMode) {
17499         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17500         if (!WhiteOnMove(forwardMostMove)) {
17501             if(blackNPS >= 0) lastTickLength = 0;
17502             blackTimeRemaining -= lastTickLength;
17503            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17504 //         if(pvInfoList[forwardMostMove].time == -1)
17505                  pvInfoList[forwardMostMove].time =               // use GUI time
17506                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17507         } else {
17508            if(whiteNPS >= 0) lastTickLength = 0;
17509            whiteTimeRemaining -= lastTickLength;
17510            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17511 //         if(pvInfoList[forwardMostMove].time == -1)
17512                  pvInfoList[forwardMostMove].time =
17513                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17514         }
17515         flagged = CheckFlags();
17516     }
17517     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17518     CheckTimeControl();
17519
17520     if (flagged || !appData.clockMode) return;
17521
17522     switch (gameMode) {
17523       case MachinePlaysBlack:
17524       case MachinePlaysWhite:
17525       case BeginningOfGame:
17526         if (pausing) return;
17527         break;
17528
17529       case EditGame:
17530       case PlayFromGameFile:
17531       case IcsExamining:
17532         return;
17533
17534       default:
17535         break;
17536     }
17537
17538     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17539         if(WhiteOnMove(forwardMostMove))
17540              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17541         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17542     }
17543
17544     tickStartTM = now;
17545     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17546       whiteTimeRemaining : blackTimeRemaining);
17547     StartClockTimer(intendedTickLength);
17548 }
17549
17550
17551 /* Stop both clocks */
17552 void
17553 StopClocks ()
17554 {
17555     long lastTickLength;
17556     TimeMark now;
17557
17558     if (!StopClockTimer()) return;
17559     if (!appData.clockMode) return;
17560
17561     GetTimeMark(&now);
17562
17563     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17564     if (WhiteOnMove(forwardMostMove)) {
17565         if(whiteNPS >= 0) lastTickLength = 0;
17566         whiteTimeRemaining -= lastTickLength;
17567         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17568     } else {
17569         if(blackNPS >= 0) lastTickLength = 0;
17570         blackTimeRemaining -= lastTickLength;
17571         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17572     }
17573     CheckFlags();
17574 }
17575
17576 /* Start clock of player on move.  Time may have been reset, so
17577    if clock is already running, stop and restart it. */
17578 void
17579 StartClocks ()
17580 {
17581     (void) StopClockTimer(); /* in case it was running already */
17582     DisplayBothClocks();
17583     if (CheckFlags()) return;
17584
17585     if (!appData.clockMode) return;
17586     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17587
17588     GetTimeMark(&tickStartTM);
17589     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17590       whiteTimeRemaining : blackTimeRemaining);
17591
17592    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17593     whiteNPS = blackNPS = -1;
17594     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17595        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17596         whiteNPS = first.nps;
17597     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17598        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17599         blackNPS = first.nps;
17600     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17601         whiteNPS = second.nps;
17602     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17603         blackNPS = second.nps;
17604     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17605
17606     StartClockTimer(intendedTickLength);
17607 }
17608
17609 char *
17610 TimeString (long ms)
17611 {
17612     long second, minute, hour, day;
17613     char *sign = "";
17614     static char buf[32];
17615
17616     if (ms > 0 && ms <= 9900) {
17617       /* convert milliseconds to tenths, rounding up */
17618       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17619
17620       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17621       return buf;
17622     }
17623
17624     /* convert milliseconds to seconds, rounding up */
17625     /* use floating point to avoid strangeness of integer division
17626        with negative dividends on many machines */
17627     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17628
17629     if (second < 0) {
17630         sign = "-";
17631         second = -second;
17632     }
17633
17634     day = second / (60 * 60 * 24);
17635     second = second % (60 * 60 * 24);
17636     hour = second / (60 * 60);
17637     second = second % (60 * 60);
17638     minute = second / 60;
17639     second = second % 60;
17640
17641     if (day > 0)
17642       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17643               sign, day, hour, minute, second);
17644     else if (hour > 0)
17645       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17646     else
17647       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17648
17649     return buf;
17650 }
17651
17652
17653 /*
17654  * This is necessary because some C libraries aren't ANSI C compliant yet.
17655  */
17656 char *
17657 StrStr (char *string, char *match)
17658 {
17659     int i, length;
17660
17661     length = strlen(match);
17662
17663     for (i = strlen(string) - length; i >= 0; i--, string++)
17664       if (!strncmp(match, string, length))
17665         return string;
17666
17667     return NULL;
17668 }
17669
17670 char *
17671 StrCaseStr (char *string, char *match)
17672 {
17673     int i, j, length;
17674
17675     length = strlen(match);
17676
17677     for (i = strlen(string) - length; i >= 0; i--, string++) {
17678         for (j = 0; j < length; j++) {
17679             if (ToLower(match[j]) != ToLower(string[j]))
17680               break;
17681         }
17682         if (j == length) return string;
17683     }
17684
17685     return NULL;
17686 }
17687
17688 #ifndef _amigados
17689 int
17690 StrCaseCmp (char *s1, char *s2)
17691 {
17692     char c1, c2;
17693
17694     for (;;) {
17695         c1 = ToLower(*s1++);
17696         c2 = ToLower(*s2++);
17697         if (c1 > c2) return 1;
17698         if (c1 < c2) return -1;
17699         if (c1 == NULLCHAR) return 0;
17700     }
17701 }
17702
17703
17704 int
17705 ToLower (int c)
17706 {
17707     return isupper(c) ? tolower(c) : c;
17708 }
17709
17710
17711 int
17712 ToUpper (int c)
17713 {
17714     return islower(c) ? toupper(c) : c;
17715 }
17716 #endif /* !_amigados    */
17717
17718 char *
17719 StrSave (char *s)
17720 {
17721   char *ret;
17722
17723   if ((ret = (char *) malloc(strlen(s) + 1)))
17724     {
17725       safeStrCpy(ret, s, strlen(s)+1);
17726     }
17727   return ret;
17728 }
17729
17730 char *
17731 StrSavePtr (char *s, char **savePtr)
17732 {
17733     if (*savePtr) {
17734         free(*savePtr);
17735     }
17736     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17737       safeStrCpy(*savePtr, s, strlen(s)+1);
17738     }
17739     return(*savePtr);
17740 }
17741
17742 char *
17743 PGNDate ()
17744 {
17745     time_t clock;
17746     struct tm *tm;
17747     char buf[MSG_SIZ];
17748
17749     clock = time((time_t *)NULL);
17750     tm = localtime(&clock);
17751     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17752             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17753     return StrSave(buf);
17754 }
17755
17756
17757 char *
17758 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17759 {
17760     int i, j, fromX, fromY, toX, toY;
17761     int whiteToPlay;
17762     char buf[MSG_SIZ];
17763     char *p, *q;
17764     int emptycount;
17765     ChessSquare piece;
17766
17767     whiteToPlay = (gameMode == EditPosition) ?
17768       !blackPlaysFirst : (move % 2 == 0);
17769     p = buf;
17770
17771     /* Piece placement data */
17772     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17773         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17774         emptycount = 0;
17775         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17776             if (boards[move][i][j] == EmptySquare) {
17777                 emptycount++;
17778             } else { ChessSquare piece = boards[move][i][j];
17779                 if (emptycount > 0) {
17780                     if(emptycount<10) /* [HGM] can be >= 10 */
17781                         *p++ = '0' + emptycount;
17782                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17783                     emptycount = 0;
17784                 }
17785                 if(PieceToChar(piece) == '+') {
17786                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17787                     *p++ = '+';
17788                     piece = (ChessSquare)(CHUDEMOTED piece);
17789                 }
17790                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17791                 if(p[-1] == '~') {
17792                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17793                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17794                     *p++ = '~';
17795                 }
17796             }
17797         }
17798         if (emptycount > 0) {
17799             if(emptycount<10) /* [HGM] can be >= 10 */
17800                 *p++ = '0' + emptycount;
17801             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17802             emptycount = 0;
17803         }
17804         *p++ = '/';
17805     }
17806     *(p - 1) = ' ';
17807
17808     /* [HGM] print Crazyhouse or Shogi holdings */
17809     if( gameInfo.holdingsWidth ) {
17810         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17811         q = p;
17812         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17813             piece = boards[move][i][BOARD_WIDTH-1];
17814             if( piece != EmptySquare )
17815               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17816                   *p++ = PieceToChar(piece);
17817         }
17818         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17819             piece = boards[move][BOARD_HEIGHT-i-1][0];
17820             if( piece != EmptySquare )
17821               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17822                   *p++ = PieceToChar(piece);
17823         }
17824
17825         if( q == p ) *p++ = '-';
17826         *p++ = ']';
17827         *p++ = ' ';
17828     }
17829
17830     /* Active color */
17831     *p++ = whiteToPlay ? 'w' : 'b';
17832     *p++ = ' ';
17833
17834   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17835     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17836   } else {
17837   if(nrCastlingRights) {
17838      int handW=0, handB=0;
17839      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
17840         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
17841         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17842      }
17843      q = p;
17844      if(appData.fischerCastling) {
17845         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
17846            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17847                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17848         } else {
17849        /* [HGM] write directly from rights */
17850            if(boards[move][CASTLING][2] != NoRights &&
17851               boards[move][CASTLING][0] != NoRights   )
17852                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17853            if(boards[move][CASTLING][2] != NoRights &&
17854               boards[move][CASTLING][1] != NoRights   )
17855                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17856         }
17857         if(handB) {
17858            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17859                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17860         } else {
17861            if(boards[move][CASTLING][5] != NoRights &&
17862               boards[move][CASTLING][3] != NoRights   )
17863                 *p++ = boards[move][CASTLING][3] + AAA;
17864            if(boards[move][CASTLING][5] != NoRights &&
17865               boards[move][CASTLING][4] != NoRights   )
17866                 *p++ = boards[move][CASTLING][4] + AAA;
17867         }
17868      } else {
17869
17870         /* [HGM] write true castling rights */
17871         if( nrCastlingRights == 6 ) {
17872             int q, k=0;
17873             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17874                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17875             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17876                  boards[move][CASTLING][2] != NoRights  );
17877             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
17878                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17879                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17880                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17881             }
17882             if(q) *p++ = 'Q';
17883             k = 0;
17884             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17885                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17886             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17887                  boards[move][CASTLING][5] != NoRights  );
17888             if(handB) {
17889                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17890                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17891                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17892             }
17893             if(q) *p++ = 'q';
17894         }
17895      }
17896      if (q == p) *p++ = '-'; /* No castling rights */
17897      *p++ = ' ';
17898   }
17899
17900   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17901      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17902      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17903     /* En passant target square */
17904     if (move > backwardMostMove) {
17905         fromX = moveList[move - 1][0] - AAA;
17906         fromY = moveList[move - 1][1] - ONE;
17907         toX = moveList[move - 1][2] - AAA;
17908         toY = moveList[move - 1][3] - ONE;
17909         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17910             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17911             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17912             fromX == toX) {
17913             /* 2-square pawn move just happened */
17914             *p++ = toX + AAA;
17915             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17916         } else {
17917             *p++ = '-';
17918         }
17919     } else if(move == backwardMostMove) {
17920         // [HGM] perhaps we should always do it like this, and forget the above?
17921         if((signed char)boards[move][EP_STATUS] >= 0) {
17922             *p++ = boards[move][EP_STATUS] + AAA;
17923             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17924         } else {
17925             *p++ = '-';
17926         }
17927     } else {
17928         *p++ = '-';
17929     }
17930     *p++ = ' ';
17931   }
17932   }
17933
17934     if(moveCounts)
17935     {   int i = 0, j=move;
17936
17937         /* [HGM] find reversible plies */
17938         if (appData.debugMode) { int k;
17939             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17940             for(k=backwardMostMove; k<=forwardMostMove; k++)
17941                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17942
17943         }
17944
17945         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17946         if( j == backwardMostMove ) i += initialRulePlies;
17947         sprintf(p, "%d ", i);
17948         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17949
17950         /* Fullmove number */
17951         sprintf(p, "%d", (move / 2) + 1);
17952     } else *--p = NULLCHAR;
17953
17954     return StrSave(buf);
17955 }
17956
17957 Boolean
17958 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17959 {
17960     int i, j, k, w=0, subst=0, shuffle=0;
17961     char *p, c;
17962     int emptycount, virgin[BOARD_FILES];
17963     ChessSquare piece;
17964
17965     p = fen;
17966
17967     /* Piece placement data */
17968     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17969         j = 0;
17970         for (;;) {
17971             if (*p == '/' || *p == ' ' || *p == '[' ) {
17972                 if(j > w) w = j;
17973                 emptycount = gameInfo.boardWidth - j;
17974                 while (emptycount--)
17975                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17976                 if (*p == '/') p++;
17977                 else if(autoSize) { // we stumbled unexpectedly into end of board
17978                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17979                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17980                     }
17981                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17982                 }
17983                 break;
17984 #if(BOARD_FILES >= 10)*0
17985             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17986                 p++; emptycount=10;
17987                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17988                 while (emptycount--)
17989                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17990 #endif
17991             } else if (*p == '*') {
17992                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17993             } else if (isdigit(*p)) {
17994                 emptycount = *p++ - '0';
17995                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17996                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17997                 while (emptycount--)
17998                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17999             } else if (*p == '<') {
18000                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18001                 else if (i != 0 || !shuffle) return FALSE;
18002                 p++;
18003             } else if (shuffle && *p == '>') {
18004                 p++; // for now ignore closing shuffle range, and assume rank-end
18005             } else if (*p == '?') {
18006                 if (j >= gameInfo.boardWidth) return FALSE;
18007                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18008                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18009             } else if (*p == '+' || isalpha(*p)) {
18010                 if (j >= gameInfo.boardWidth) return FALSE;
18011                 if(*p=='+') {
18012                     piece = CharToPiece(*++p);
18013                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18014                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
18015                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18016                 } else piece = CharToPiece(*p++);
18017
18018                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18019                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18020                     piece = (ChessSquare) (PROMOTED piece);
18021                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18022                     p++;
18023                 }
18024                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18025             } else {
18026                 return FALSE;
18027             }
18028         }
18029     }
18030     while (*p == '/' || *p == ' ') p++;
18031
18032     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
18033
18034     /* [HGM] by default clear Crazyhouse holdings, if present */
18035     if(gameInfo.holdingsWidth) {
18036        for(i=0; i<BOARD_HEIGHT; i++) {
18037            board[i][0]             = EmptySquare; /* black holdings */
18038            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18039            board[i][1]             = (ChessSquare) 0; /* black counts */
18040            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18041        }
18042     }
18043
18044     /* [HGM] look for Crazyhouse holdings here */
18045     while(*p==' ') p++;
18046     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18047         int swap=0, wcnt=0, bcnt=0;
18048         if(*p == '[') p++;
18049         if(*p == '<') swap++, p++;
18050         if(*p == '-' ) p++; /* empty holdings */ else {
18051             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18052             /* if we would allow FEN reading to set board size, we would   */
18053             /* have to add holdings and shift the board read so far here   */
18054             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18055                 p++;
18056                 if((int) piece >= (int) BlackPawn ) {
18057                     i = (int)piece - (int)BlackPawn;
18058                     i = PieceToNumber((ChessSquare)i);
18059                     if( i >= gameInfo.holdingsSize ) return FALSE;
18060                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18061                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18062                     bcnt++;
18063                 } else {
18064                     i = (int)piece - (int)WhitePawn;
18065                     i = PieceToNumber((ChessSquare)i);
18066                     if( i >= gameInfo.holdingsSize ) return FALSE;
18067                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18068                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18069                     wcnt++;
18070                 }
18071             }
18072             if(subst) { // substitute back-rank question marks by holdings pieces
18073                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18074                     int k, m, n = bcnt + 1;
18075                     if(board[0][j] == ClearBoard) {
18076                         if(!wcnt) return FALSE;
18077                         n = rand() % wcnt;
18078                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18079                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18080                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18081                             break;
18082                         }
18083                     }
18084                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18085                         if(!bcnt) return FALSE;
18086                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18087                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18088                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18089                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18090                             break;
18091                         }
18092                     }
18093                 }
18094                 subst = 0;
18095             }
18096         }
18097         if(*p == ']') p++;
18098     }
18099
18100     if(subst) return FALSE; // substitution requested, but no holdings
18101
18102     while(*p == ' ') p++;
18103
18104     /* Active color */
18105     c = *p++;
18106     if(appData.colorNickNames) {
18107       if( c == appData.colorNickNames[0] ) c = 'w'; else
18108       if( c == appData.colorNickNames[1] ) c = 'b';
18109     }
18110     switch (c) {
18111       case 'w':
18112         *blackPlaysFirst = FALSE;
18113         break;
18114       case 'b':
18115         *blackPlaysFirst = TRUE;
18116         break;
18117       default:
18118         return FALSE;
18119     }
18120
18121     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18122     /* return the extra info in global variiables             */
18123
18124     /* set defaults in case FEN is incomplete */
18125     board[EP_STATUS] = EP_UNKNOWN;
18126     for(i=0; i<nrCastlingRights; i++ ) {
18127         board[CASTLING][i] =
18128             appData.fischerCastling ? NoRights : initialRights[i];
18129     }   /* assume possible unless obviously impossible */
18130     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18131     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18132     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18133                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18134     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18135     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18136     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18137                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18138     FENrulePlies = 0;
18139
18140     while(*p==' ') p++;
18141     if(nrCastlingRights) {
18142       int fischer = 0;
18143       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18144       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18145           /* castling indicator present, so default becomes no castlings */
18146           for(i=0; i<nrCastlingRights; i++ ) {
18147                  board[CASTLING][i] = NoRights;
18148           }
18149       }
18150       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18151              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18152              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18153              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18154         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18155
18156         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18157             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
18158             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
18159         }
18160         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18161             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18162         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18163                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18164         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18165                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18166         switch(c) {
18167           case'K':
18168               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
18169               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18170               board[CASTLING][2] = whiteKingFile;
18171               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18172               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18173               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18174               break;
18175           case'Q':
18176               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
18177               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18178               board[CASTLING][2] = whiteKingFile;
18179               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18180               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18181               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18182               break;
18183           case'k':
18184               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18185               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18186               board[CASTLING][5] = blackKingFile;
18187               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18188               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18189               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18190               break;
18191           case'q':
18192               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18193               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18194               board[CASTLING][5] = blackKingFile;
18195               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18196               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18197               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18198           case '-':
18199               break;
18200           default: /* FRC castlings */
18201               if(c >= 'a') { /* black rights */
18202                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18203                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18204                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18205                   if(i == BOARD_RGHT) break;
18206                   board[CASTLING][5] = i;
18207                   c -= AAA;
18208                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18209                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18210                   if(c > i)
18211                       board[CASTLING][3] = c;
18212                   else
18213                       board[CASTLING][4] = c;
18214               } else { /* white rights */
18215                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18216                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18217                     if(board[0][i] == WhiteKing) break;
18218                   if(i == BOARD_RGHT) break;
18219                   board[CASTLING][2] = i;
18220                   c -= AAA - 'a' + 'A';
18221                   if(board[0][c] >= WhiteKing) break;
18222                   if(c > i)
18223                       board[CASTLING][0] = c;
18224                   else
18225                       board[CASTLING][1] = c;
18226               }
18227         }
18228       }
18229       for(i=0; i<nrCastlingRights; i++)
18230         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18231       if(gameInfo.variant == VariantSChess)
18232         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18233       if(fischer && shuffle) appData.fischerCastling = TRUE;
18234     if (appData.debugMode) {
18235         fprintf(debugFP, "FEN castling rights:");
18236         for(i=0; i<nrCastlingRights; i++)
18237         fprintf(debugFP, " %d", board[CASTLING][i]);
18238         fprintf(debugFP, "\n");
18239     }
18240
18241       while(*p==' ') p++;
18242     }
18243
18244     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18245
18246     /* read e.p. field in games that know e.p. capture */
18247     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18248        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18249        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18250       if(*p=='-') {
18251         p++; board[EP_STATUS] = EP_NONE;
18252       } else {
18253          char c = *p++ - AAA;
18254
18255          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18256          if(*p >= '0' && *p <='9') p++;
18257          board[EP_STATUS] = c;
18258       }
18259     }
18260
18261
18262     if(sscanf(p, "%d", &i) == 1) {
18263         FENrulePlies = i; /* 50-move ply counter */
18264         /* (The move number is still ignored)    */
18265     }
18266
18267     return TRUE;
18268 }
18269
18270 void
18271 EditPositionPasteFEN (char *fen)
18272 {
18273   if (fen != NULL) {
18274     Board initial_position;
18275
18276     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18277       DisplayError(_("Bad FEN position in clipboard"), 0);
18278       return ;
18279     } else {
18280       int savedBlackPlaysFirst = blackPlaysFirst;
18281       EditPositionEvent();
18282       blackPlaysFirst = savedBlackPlaysFirst;
18283       CopyBoard(boards[0], initial_position);
18284       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18285       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18286       DisplayBothClocks();
18287       DrawPosition(FALSE, boards[currentMove]);
18288     }
18289   }
18290 }
18291
18292 static char cseq[12] = "\\   ";
18293
18294 Boolean
18295 set_cont_sequence (char *new_seq)
18296 {
18297     int len;
18298     Boolean ret;
18299
18300     // handle bad attempts to set the sequence
18301         if (!new_seq)
18302                 return 0; // acceptable error - no debug
18303
18304     len = strlen(new_seq);
18305     ret = (len > 0) && (len < sizeof(cseq));
18306     if (ret)
18307       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18308     else if (appData.debugMode)
18309       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18310     return ret;
18311 }
18312
18313 /*
18314     reformat a source message so words don't cross the width boundary.  internal
18315     newlines are not removed.  returns the wrapped size (no null character unless
18316     included in source message).  If dest is NULL, only calculate the size required
18317     for the dest buffer.  lp argument indicats line position upon entry, and it's
18318     passed back upon exit.
18319 */
18320 int
18321 wrap (char *dest, char *src, int count, int width, int *lp)
18322 {
18323     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18324
18325     cseq_len = strlen(cseq);
18326     old_line = line = *lp;
18327     ansi = len = clen = 0;
18328
18329     for (i=0; i < count; i++)
18330     {
18331         if (src[i] == '\033')
18332             ansi = 1;
18333
18334         // if we hit the width, back up
18335         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18336         {
18337             // store i & len in case the word is too long
18338             old_i = i, old_len = len;
18339
18340             // find the end of the last word
18341             while (i && src[i] != ' ' && src[i] != '\n')
18342             {
18343                 i--;
18344                 len--;
18345             }
18346
18347             // word too long?  restore i & len before splitting it
18348             if ((old_i-i+clen) >= width)
18349             {
18350                 i = old_i;
18351                 len = old_len;
18352             }
18353
18354             // extra space?
18355             if (i && src[i-1] == ' ')
18356                 len--;
18357
18358             if (src[i] != ' ' && src[i] != '\n')
18359             {
18360                 i--;
18361                 if (len)
18362                     len--;
18363             }
18364
18365             // now append the newline and continuation sequence
18366             if (dest)
18367                 dest[len] = '\n';
18368             len++;
18369             if (dest)
18370                 strncpy(dest+len, cseq, cseq_len);
18371             len += cseq_len;
18372             line = cseq_len;
18373             clen = cseq_len;
18374             continue;
18375         }
18376
18377         if (dest)
18378             dest[len] = src[i];
18379         len++;
18380         if (!ansi)
18381             line++;
18382         if (src[i] == '\n')
18383             line = 0;
18384         if (src[i] == 'm')
18385             ansi = 0;
18386     }
18387     if (dest && appData.debugMode)
18388     {
18389         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18390             count, width, line, len, *lp);
18391         show_bytes(debugFP, src, count);
18392         fprintf(debugFP, "\ndest: ");
18393         show_bytes(debugFP, dest, len);
18394         fprintf(debugFP, "\n");
18395     }
18396     *lp = dest ? line : old_line;
18397
18398     return len;
18399 }
18400
18401 // [HGM] vari: routines for shelving variations
18402 Boolean modeRestore = FALSE;
18403
18404 void
18405 PushInner (int firstMove, int lastMove)
18406 {
18407         int i, j, nrMoves = lastMove - firstMove;
18408
18409         // push current tail of game on stack
18410         savedResult[storedGames] = gameInfo.result;
18411         savedDetails[storedGames] = gameInfo.resultDetails;
18412         gameInfo.resultDetails = NULL;
18413         savedFirst[storedGames] = firstMove;
18414         savedLast [storedGames] = lastMove;
18415         savedFramePtr[storedGames] = framePtr;
18416         framePtr -= nrMoves; // reserve space for the boards
18417         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18418             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18419             for(j=0; j<MOVE_LEN; j++)
18420                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18421             for(j=0; j<2*MOVE_LEN; j++)
18422                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18423             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18424             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18425             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18426             pvInfoList[firstMove+i-1].depth = 0;
18427             commentList[framePtr+i] = commentList[firstMove+i];
18428             commentList[firstMove+i] = NULL;
18429         }
18430
18431         storedGames++;
18432         forwardMostMove = firstMove; // truncate game so we can start variation
18433 }
18434
18435 void
18436 PushTail (int firstMove, int lastMove)
18437 {
18438         if(appData.icsActive) { // only in local mode
18439                 forwardMostMove = currentMove; // mimic old ICS behavior
18440                 return;
18441         }
18442         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18443
18444         PushInner(firstMove, lastMove);
18445         if(storedGames == 1) GreyRevert(FALSE);
18446         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18447 }
18448
18449 void
18450 PopInner (Boolean annotate)
18451 {
18452         int i, j, nrMoves;
18453         char buf[8000], moveBuf[20];
18454
18455         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18456         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18457         nrMoves = savedLast[storedGames] - currentMove;
18458         if(annotate) {
18459                 int cnt = 10;
18460                 if(!WhiteOnMove(currentMove))
18461                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18462                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18463                 for(i=currentMove; i<forwardMostMove; i++) {
18464                         if(WhiteOnMove(i))
18465                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18466                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18467                         strcat(buf, moveBuf);
18468                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18469                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18470                 }
18471                 strcat(buf, ")");
18472         }
18473         for(i=1; i<=nrMoves; i++) { // copy last variation back
18474             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18475             for(j=0; j<MOVE_LEN; j++)
18476                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18477             for(j=0; j<2*MOVE_LEN; j++)
18478                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18479             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18480             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18481             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18482             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18483             commentList[currentMove+i] = commentList[framePtr+i];
18484             commentList[framePtr+i] = NULL;
18485         }
18486         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18487         framePtr = savedFramePtr[storedGames];
18488         gameInfo.result = savedResult[storedGames];
18489         if(gameInfo.resultDetails != NULL) {
18490             free(gameInfo.resultDetails);
18491       }
18492         gameInfo.resultDetails = savedDetails[storedGames];
18493         forwardMostMove = currentMove + nrMoves;
18494 }
18495
18496 Boolean
18497 PopTail (Boolean annotate)
18498 {
18499         if(appData.icsActive) return FALSE; // only in local mode
18500         if(!storedGames) return FALSE; // sanity
18501         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18502
18503         PopInner(annotate);
18504         if(currentMove < forwardMostMove) ForwardEvent(); else
18505         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18506
18507         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18508         return TRUE;
18509 }
18510
18511 void
18512 CleanupTail ()
18513 {       // remove all shelved variations
18514         int i;
18515         for(i=0; i<storedGames; i++) {
18516             if(savedDetails[i])
18517                 free(savedDetails[i]);
18518             savedDetails[i] = NULL;
18519         }
18520         for(i=framePtr; i<MAX_MOVES; i++) {
18521                 if(commentList[i]) free(commentList[i]);
18522                 commentList[i] = NULL;
18523         }
18524         framePtr = MAX_MOVES-1;
18525         storedGames = 0;
18526 }
18527
18528 void
18529 LoadVariation (int index, char *text)
18530 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18531         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18532         int level = 0, move;
18533
18534         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18535         // first find outermost bracketing variation
18536         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18537             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18538                 if(*p == '{') wait = '}'; else
18539                 if(*p == '[') wait = ']'; else
18540                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18541                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18542             }
18543             if(*p == wait) wait = NULLCHAR; // closing ]} found
18544             p++;
18545         }
18546         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18547         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18548         end[1] = NULLCHAR; // clip off comment beyond variation
18549         ToNrEvent(currentMove-1);
18550         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18551         // kludge: use ParsePV() to append variation to game
18552         move = currentMove;
18553         ParsePV(start, TRUE, TRUE);
18554         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18555         ClearPremoveHighlights();
18556         CommentPopDown();
18557         ToNrEvent(currentMove+1);
18558 }
18559
18560 void
18561 LoadTheme ()
18562 {
18563     char *p, *q, buf[MSG_SIZ];
18564     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18565         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18566         ParseArgsFromString(buf);
18567         ActivateTheme(TRUE); // also redo colors
18568         return;
18569     }
18570     p = nickName;
18571     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18572     {
18573         int len;
18574         q = appData.themeNames;
18575         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18576       if(appData.useBitmaps) {
18577         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18578                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18579                 appData.liteBackTextureMode,
18580                 appData.darkBackTextureMode );
18581       } else {
18582         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18583                 Col2Text(2),   // lightSquareColor
18584                 Col2Text(3) ); // darkSquareColor
18585       }
18586       if(appData.useBorder) {
18587         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18588                 appData.border);
18589       } else {
18590         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18591       }
18592       if(appData.useFont) {
18593         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18594                 appData.renderPiecesWithFont,
18595                 appData.fontToPieceTable,
18596                 Col2Text(9),    // appData.fontBackColorWhite
18597                 Col2Text(10) ); // appData.fontForeColorBlack
18598       } else {
18599         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18600                 appData.pieceDirectory);
18601         if(!appData.pieceDirectory[0])
18602           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18603                 Col2Text(0),   // whitePieceColor
18604                 Col2Text(1) ); // blackPieceColor
18605       }
18606       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18607                 Col2Text(4),   // highlightSquareColor
18608                 Col2Text(5) ); // premoveHighlightColor
18609         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18610         if(insert != q) insert[-1] = NULLCHAR;
18611         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18612         if(q)   free(q);
18613     }
18614     ActivateTheme(FALSE);
18615 }