Also set e.p. rights on move of Lance
[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 || gameInfo.variant == VariantASEAN)
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 || gameInfo.variant == VariantASEAN)
6520         result = WhiteFerz; // no choice
6521     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6522         result= WhiteKing; // in Suicide Q is the last thing we want
6523     else if(gameInfo.variant == VariantSpartan)
6524         result = white ? WhiteQueen : WhiteAngel;
6525     else result = WhiteQueen;
6526     if(!white) result = WHITE_TO_BLACK result;
6527     return result;
6528 }
6529
6530 static int autoQueen; // [HGM] oneclick
6531
6532 int
6533 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6534 {
6535     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6536     /* [HGM] add Shogi promotions */
6537     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6538     ChessSquare piece, partner;
6539     ChessMove moveType;
6540     Boolean premove;
6541
6542     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6543     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6544
6545     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6546       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6547         return FALSE;
6548
6549     piece = boards[currentMove][fromY][fromX];
6550     if(gameInfo.variant == VariantChu) {
6551         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6552         promotionZoneSize = BOARD_HEIGHT/3;
6553         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6554     } else if(gameInfo.variant == VariantShogi) {
6555         promotionZoneSize = BOARD_HEIGHT/3;
6556         highestPromotingPiece = (int)WhiteAlfil;
6557     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6558         promotionZoneSize = 3;
6559     }
6560
6561     // Treat Lance as Pawn when it is not representing Amazon or Lance
6562     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6563         if(piece == WhiteLance) piece = WhitePawn; else
6564         if(piece == BlackLance) piece = BlackPawn;
6565     }
6566
6567     // next weed out all moves that do not touch the promotion zone at all
6568     if((int)piece >= BlackPawn) {
6569         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6570              return FALSE;
6571         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6572         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6573     } else {
6574         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6575            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6576         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6577              return FALSE;
6578     }
6579
6580     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6581
6582     // weed out mandatory Shogi promotions
6583     if(gameInfo.variant == VariantShogi) {
6584         if(piece >= BlackPawn) {
6585             if(toY == 0 && piece == BlackPawn ||
6586                toY == 0 && piece == BlackQueen ||
6587                toY <= 1 && piece == BlackKnight) {
6588                 *promoChoice = '+';
6589                 return FALSE;
6590             }
6591         } else {
6592             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6593                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6594                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6595                 *promoChoice = '+';
6596                 return FALSE;
6597             }
6598         }
6599     }
6600
6601     // weed out obviously illegal Pawn moves
6602     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6603         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6604         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6605         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6606         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6607         // note we are not allowed to test for valid (non-)capture, due to premove
6608     }
6609
6610     // we either have a choice what to promote to, or (in Shogi) whether to promote
6611     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6612        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6613         ChessSquare p=BlackFerz;  // no choice
6614         while(p < EmptySquare) {  //but make sure we use piece that exists
6615             *promoChoice = PieceToChar(p++);
6616             if(*promoChoice != '.') break;
6617         }
6618         return FALSE;
6619     }
6620     // no sense asking what we must promote to if it is going to explode...
6621     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6622         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6623         return FALSE;
6624     }
6625     // give caller the default choice even if we will not make it
6626     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6627     partner = piece; // pieces can promote if the pieceToCharTable says so
6628     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6629     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6630     if(        sweepSelect && gameInfo.variant != VariantGreat
6631                            && gameInfo.variant != VariantGrand
6632                            && gameInfo.variant != VariantSuper) return FALSE;
6633     if(autoQueen) return FALSE; // predetermined
6634
6635     // suppress promotion popup on illegal moves that are not premoves
6636     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6637               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6638     if(appData.testLegality && !premove) {
6639         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6640                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6641         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6642         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6643             return FALSE;
6644     }
6645
6646     return TRUE;
6647 }
6648
6649 int
6650 InPalace (int row, int column)
6651 {   /* [HGM] for Xiangqi */
6652     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6653          column < (BOARD_WIDTH + 4)/2 &&
6654          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6655     return FALSE;
6656 }
6657
6658 int
6659 PieceForSquare (int x, int y)
6660 {
6661   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6662      return -1;
6663   else
6664      return boards[currentMove][y][x];
6665 }
6666
6667 int
6668 OKToStartUserMove (int x, int y)
6669 {
6670     ChessSquare from_piece;
6671     int white_piece;
6672
6673     if (matchMode) return FALSE;
6674     if (gameMode == EditPosition) return TRUE;
6675
6676     if (x >= 0 && y >= 0)
6677       from_piece = boards[currentMove][y][x];
6678     else
6679       from_piece = EmptySquare;
6680
6681     if (from_piece == EmptySquare) return FALSE;
6682
6683     white_piece = (int)from_piece >= (int)WhitePawn &&
6684       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6685
6686     switch (gameMode) {
6687       case AnalyzeFile:
6688       case TwoMachinesPlay:
6689       case EndOfGame:
6690         return FALSE;
6691
6692       case IcsObserving:
6693       case IcsIdle:
6694         return FALSE;
6695
6696       case MachinePlaysWhite:
6697       case IcsPlayingBlack:
6698         if (appData.zippyPlay) return FALSE;
6699         if (white_piece) {
6700             DisplayMoveError(_("You are playing Black"));
6701             return FALSE;
6702         }
6703         break;
6704
6705       case MachinePlaysBlack:
6706       case IcsPlayingWhite:
6707         if (appData.zippyPlay) return FALSE;
6708         if (!white_piece) {
6709             DisplayMoveError(_("You are playing White"));
6710             return FALSE;
6711         }
6712         break;
6713
6714       case PlayFromGameFile:
6715             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6716       case EditGame:
6717         if (!white_piece && WhiteOnMove(currentMove)) {
6718             DisplayMoveError(_("It is White's turn"));
6719             return FALSE;
6720         }
6721         if (white_piece && !WhiteOnMove(currentMove)) {
6722             DisplayMoveError(_("It is Black's turn"));
6723             return FALSE;
6724         }
6725         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6726             /* Editing correspondence game history */
6727             /* Could disallow this or prompt for confirmation */
6728             cmailOldMove = -1;
6729         }
6730         break;
6731
6732       case BeginningOfGame:
6733         if (appData.icsActive) return FALSE;
6734         if (!appData.noChessProgram) {
6735             if (!white_piece) {
6736                 DisplayMoveError(_("You are playing White"));
6737                 return FALSE;
6738             }
6739         }
6740         break;
6741
6742       case Training:
6743         if (!white_piece && WhiteOnMove(currentMove)) {
6744             DisplayMoveError(_("It is White's turn"));
6745             return FALSE;
6746         }
6747         if (white_piece && !WhiteOnMove(currentMove)) {
6748             DisplayMoveError(_("It is Black's turn"));
6749             return FALSE;
6750         }
6751         break;
6752
6753       default:
6754       case IcsExamining:
6755         break;
6756     }
6757     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6758         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6759         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6760         && gameMode != AnalyzeFile && gameMode != Training) {
6761         DisplayMoveError(_("Displayed position is not current"));
6762         return FALSE;
6763     }
6764     return TRUE;
6765 }
6766
6767 Boolean
6768 OnlyMove (int *x, int *y, Boolean captures)
6769 {
6770     DisambiguateClosure cl;
6771     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6772     switch(gameMode) {
6773       case MachinePlaysBlack:
6774       case IcsPlayingWhite:
6775       case BeginningOfGame:
6776         if(!WhiteOnMove(currentMove)) return FALSE;
6777         break;
6778       case MachinePlaysWhite:
6779       case IcsPlayingBlack:
6780         if(WhiteOnMove(currentMove)) return FALSE;
6781         break;
6782       case EditGame:
6783         break;
6784       default:
6785         return FALSE;
6786     }
6787     cl.pieceIn = EmptySquare;
6788     cl.rfIn = *y;
6789     cl.ffIn = *x;
6790     cl.rtIn = -1;
6791     cl.ftIn = -1;
6792     cl.promoCharIn = NULLCHAR;
6793     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6794     if( cl.kind == NormalMove ||
6795         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6796         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6797         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6798       fromX = cl.ff;
6799       fromY = cl.rf;
6800       *x = cl.ft;
6801       *y = cl.rt;
6802       return TRUE;
6803     }
6804     if(cl.kind != ImpossibleMove) return FALSE;
6805     cl.pieceIn = EmptySquare;
6806     cl.rfIn = -1;
6807     cl.ffIn = -1;
6808     cl.rtIn = *y;
6809     cl.ftIn = *x;
6810     cl.promoCharIn = NULLCHAR;
6811     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6812     if( cl.kind == NormalMove ||
6813         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6814         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6815         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6816       fromX = cl.ff;
6817       fromY = cl.rf;
6818       *x = cl.ft;
6819       *y = cl.rt;
6820       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6821       return TRUE;
6822     }
6823     return FALSE;
6824 }
6825
6826 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6827 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6828 int lastLoadGameUseList = FALSE;
6829 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6830 ChessMove lastLoadGameStart = EndOfFile;
6831 int doubleClick;
6832 Boolean addToBookFlag;
6833
6834 void
6835 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6836 {
6837     ChessMove moveType;
6838     ChessSquare pup;
6839     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6840
6841     /* Check if the user is playing in turn.  This is complicated because we
6842        let the user "pick up" a piece before it is his turn.  So the piece he
6843        tried to pick up may have been captured by the time he puts it down!
6844        Therefore we use the color the user is supposed to be playing in this
6845        test, not the color of the piece that is currently on the starting
6846        square---except in EditGame mode, where the user is playing both
6847        sides; fortunately there the capture race can't happen.  (It can
6848        now happen in IcsExamining mode, but that's just too bad.  The user
6849        will get a somewhat confusing message in that case.)
6850        */
6851
6852     switch (gameMode) {
6853       case AnalyzeFile:
6854       case TwoMachinesPlay:
6855       case EndOfGame:
6856       case IcsObserving:
6857       case IcsIdle:
6858         /* We switched into a game mode where moves are not accepted,
6859            perhaps while the mouse button was down. */
6860         return;
6861
6862       case MachinePlaysWhite:
6863         /* User is moving for Black */
6864         if (WhiteOnMove(currentMove)) {
6865             DisplayMoveError(_("It is White's turn"));
6866             return;
6867         }
6868         break;
6869
6870       case MachinePlaysBlack:
6871         /* User is moving for White */
6872         if (!WhiteOnMove(currentMove)) {
6873             DisplayMoveError(_("It is Black's turn"));
6874             return;
6875         }
6876         break;
6877
6878       case PlayFromGameFile:
6879             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6880       case EditGame:
6881       case IcsExamining:
6882       case BeginningOfGame:
6883       case AnalyzeMode:
6884       case Training:
6885         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6886         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6887             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6888             /* User is moving for Black */
6889             if (WhiteOnMove(currentMove)) {
6890                 DisplayMoveError(_("It is White's turn"));
6891                 return;
6892             }
6893         } else {
6894             /* User is moving for White */
6895             if (!WhiteOnMove(currentMove)) {
6896                 DisplayMoveError(_("It is Black's turn"));
6897                 return;
6898             }
6899         }
6900         break;
6901
6902       case IcsPlayingBlack:
6903         /* User is moving for Black */
6904         if (WhiteOnMove(currentMove)) {
6905             if (!appData.premove) {
6906                 DisplayMoveError(_("It is White's turn"));
6907             } else if (toX >= 0 && toY >= 0) {
6908                 premoveToX = toX;
6909                 premoveToY = toY;
6910                 premoveFromX = fromX;
6911                 premoveFromY = fromY;
6912                 premovePromoChar = promoChar;
6913                 gotPremove = 1;
6914                 if (appData.debugMode)
6915                     fprintf(debugFP, "Got premove: fromX %d,"
6916                             "fromY %d, toX %d, toY %d\n",
6917                             fromX, fromY, toX, toY);
6918             }
6919             return;
6920         }
6921         break;
6922
6923       case IcsPlayingWhite:
6924         /* User is moving for White */
6925         if (!WhiteOnMove(currentMove)) {
6926             if (!appData.premove) {
6927                 DisplayMoveError(_("It is Black's turn"));
6928             } else if (toX >= 0 && toY >= 0) {
6929                 premoveToX = toX;
6930                 premoveToY = toY;
6931                 premoveFromX = fromX;
6932                 premoveFromY = fromY;
6933                 premovePromoChar = promoChar;
6934                 gotPremove = 1;
6935                 if (appData.debugMode)
6936                     fprintf(debugFP, "Got premove: fromX %d,"
6937                             "fromY %d, toX %d, toY %d\n",
6938                             fromX, fromY, toX, toY);
6939             }
6940             return;
6941         }
6942         break;
6943
6944       default:
6945         break;
6946
6947       case EditPosition:
6948         /* EditPosition, empty square, or different color piece;
6949            click-click move is possible */
6950         if (toX == -2 || toY == -2) {
6951             boards[0][fromY][fromX] = EmptySquare;
6952             DrawPosition(FALSE, boards[currentMove]);
6953             return;
6954         } else if (toX >= 0 && toY >= 0) {
6955             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
6956                 ChessSquare q, p = boards[0][rf][ff];
6957                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
6958                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
6959                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
6960                 if(PieceToChar(q) == '+') gatingPiece = p;
6961             }
6962             boards[0][toY][toX] = boards[0][fromY][fromX];
6963             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6964                 if(boards[0][fromY][0] != EmptySquare) {
6965                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6966                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6967                 }
6968             } else
6969             if(fromX == BOARD_RGHT+1) {
6970                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6971                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6972                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6973                 }
6974             } else
6975             boards[0][fromY][fromX] = gatingPiece;
6976             DrawPosition(FALSE, boards[currentMove]);
6977             return;
6978         }
6979         return;
6980     }
6981
6982     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
6983     pup = boards[currentMove][toY][toX];
6984
6985     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6986     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6987          if( pup != EmptySquare ) return;
6988          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6989            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6990                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6991            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6992            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6993            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6994            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6995          fromY = DROP_RANK;
6996     }
6997
6998     /* [HGM] always test for legality, to get promotion info */
6999     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7000                                          fromY, fromX, toY, toX, promoChar);
7001
7002     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7003
7004     /* [HGM] but possibly ignore an IllegalMove result */
7005     if (appData.testLegality) {
7006         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7007             DisplayMoveError(_("Illegal move"));
7008             return;
7009         }
7010     }
7011
7012     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7013         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7014              ClearPremoveHighlights(); // was included
7015         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7016         return;
7017     }
7018
7019     if(addToBookFlag) { // adding moves to book
7020         char buf[MSG_SIZ], move[MSG_SIZ];
7021         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7022         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7023         AddBookMove(buf);
7024         addToBookFlag = FALSE;
7025         ClearHighlights();
7026         return;
7027     }
7028
7029     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7030 }
7031
7032 /* Common tail of UserMoveEvent and DropMenuEvent */
7033 int
7034 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7035 {
7036     char *bookHit = 0;
7037
7038     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7039         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7040         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7041         if(WhiteOnMove(currentMove)) {
7042             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7043         } else {
7044             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7045         }
7046     }
7047
7048     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7049        move type in caller when we know the move is a legal promotion */
7050     if(moveType == NormalMove && promoChar)
7051         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7052
7053     /* [HGM] <popupFix> The following if has been moved here from
7054        UserMoveEvent(). Because it seemed to belong here (why not allow
7055        piece drops in training games?), and because it can only be
7056        performed after it is known to what we promote. */
7057     if (gameMode == Training) {
7058       /* compare the move played on the board to the next move in the
7059        * game. If they match, display the move and the opponent's response.
7060        * If they don't match, display an error message.
7061        */
7062       int saveAnimate;
7063       Board testBoard;
7064       CopyBoard(testBoard, boards[currentMove]);
7065       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7066
7067       if (CompareBoards(testBoard, boards[currentMove+1])) {
7068         ForwardInner(currentMove+1);
7069
7070         /* Autoplay the opponent's response.
7071          * if appData.animate was TRUE when Training mode was entered,
7072          * the response will be animated.
7073          */
7074         saveAnimate = appData.animate;
7075         appData.animate = animateTraining;
7076         ForwardInner(currentMove+1);
7077         appData.animate = saveAnimate;
7078
7079         /* check for the end of the game */
7080         if (currentMove >= forwardMostMove) {
7081           gameMode = PlayFromGameFile;
7082           ModeHighlight();
7083           SetTrainingModeOff();
7084           DisplayInformation(_("End of game"));
7085         }
7086       } else {
7087         DisplayError(_("Incorrect move"), 0);
7088       }
7089       return 1;
7090     }
7091
7092   /* Ok, now we know that the move is good, so we can kill
7093      the previous line in Analysis Mode */
7094   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7095                                 && currentMove < forwardMostMove) {
7096     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7097     else forwardMostMove = currentMove;
7098   }
7099
7100   ClearMap();
7101
7102   /* If we need the chess program but it's dead, restart it */
7103   ResurrectChessProgram();
7104
7105   /* A user move restarts a paused game*/
7106   if (pausing)
7107     PauseEvent();
7108
7109   thinkOutput[0] = NULLCHAR;
7110
7111   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7112
7113   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7114     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7115     return 1;
7116   }
7117
7118   if (gameMode == BeginningOfGame) {
7119     if (appData.noChessProgram) {
7120       gameMode = EditGame;
7121       SetGameInfo();
7122     } else {
7123       char buf[MSG_SIZ];
7124       gameMode = MachinePlaysBlack;
7125       StartClocks();
7126       SetGameInfo();
7127       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7128       DisplayTitle(buf);
7129       if (first.sendName) {
7130         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7131         SendToProgram(buf, &first);
7132       }
7133       StartClocks();
7134     }
7135     ModeHighlight();
7136   }
7137
7138   /* Relay move to ICS or chess engine */
7139   if (appData.icsActive) {
7140     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7141         gameMode == IcsExamining) {
7142       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7143         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7144         SendToICS("draw ");
7145         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7146       }
7147       // also send plain move, in case ICS does not understand atomic claims
7148       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7149       ics_user_moved = 1;
7150     }
7151   } else {
7152     if (first.sendTime && (gameMode == BeginningOfGame ||
7153                            gameMode == MachinePlaysWhite ||
7154                            gameMode == MachinePlaysBlack)) {
7155       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7156     }
7157     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7158          // [HGM] book: if program might be playing, let it use book
7159         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7160         first.maybeThinking = TRUE;
7161     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7162         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7163         SendBoard(&first, currentMove+1);
7164         if(second.analyzing) {
7165             if(!second.useSetboard) SendToProgram("undo\n", &second);
7166             SendBoard(&second, currentMove+1);
7167         }
7168     } else {
7169         SendMoveToProgram(forwardMostMove-1, &first);
7170         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7171     }
7172     if (currentMove == cmailOldMove + 1) {
7173       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7174     }
7175   }
7176
7177   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7178
7179   switch (gameMode) {
7180   case EditGame:
7181     if(appData.testLegality)
7182     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7183     case MT_NONE:
7184     case MT_CHECK:
7185       break;
7186     case MT_CHECKMATE:
7187     case MT_STAINMATE:
7188       if (WhiteOnMove(currentMove)) {
7189         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7190       } else {
7191         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7192       }
7193       break;
7194     case MT_STALEMATE:
7195       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7196       break;
7197     }
7198     break;
7199
7200   case MachinePlaysBlack:
7201   case MachinePlaysWhite:
7202     /* disable certain menu options while machine is thinking */
7203     SetMachineThinkingEnables();
7204     break;
7205
7206   default:
7207     break;
7208   }
7209
7210   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7211   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7212
7213   if(bookHit) { // [HGM] book: simulate book reply
7214         static char bookMove[MSG_SIZ]; // a bit generous?
7215
7216         programStats.nodes = programStats.depth = programStats.time =
7217         programStats.score = programStats.got_only_move = 0;
7218         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7219
7220         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7221         strcat(bookMove, bookHit);
7222         HandleMachineMove(bookMove, &first);
7223   }
7224   return 1;
7225 }
7226
7227 void
7228 MarkByFEN(char *fen)
7229 {
7230         int r, f;
7231         if(!appData.markers || !appData.highlightDragging) return;
7232         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7233         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7234         while(*fen) {
7235             int s = 0;
7236             marker[r][f] = 0;
7237             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7238             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7239             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7240             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7241             if(*fen == 'T') marker[r][f++] = 0; else
7242             if(*fen == 'Y') marker[r][f++] = 1; else
7243             if(*fen == 'G') marker[r][f++] = 3; else
7244             if(*fen == 'B') marker[r][f++] = 4; else
7245             if(*fen == 'C') marker[r][f++] = 5; else
7246             if(*fen == 'M') marker[r][f++] = 6; else
7247             if(*fen == 'W') marker[r][f++] = 7; else
7248             if(*fen == 'D') marker[r][f++] = 8; else
7249             if(*fen == 'R') marker[r][f++] = 2; else {
7250                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7251               f += s; fen -= s>0;
7252             }
7253             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7254             if(r < 0) break;
7255             fen++;
7256         }
7257         DrawPosition(TRUE, NULL);
7258 }
7259
7260 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7261
7262 void
7263 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7264 {
7265     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7266     Markers *m = (Markers *) closure;
7267     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7268         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7269                          || kind == WhiteCapturesEnPassant
7270                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 1;
7271     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
7272 }
7273
7274 static int hoverSavedValid;
7275
7276 void
7277 MarkTargetSquares (int clear)
7278 {
7279   int x, y, sum=0;
7280   if(clear) { // no reason to ever suppress clearing
7281     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7282     hoverSavedValid = 0;
7283     if(!sum) return; // nothing was cleared,no redraw needed
7284   } else {
7285     int capt = 0;
7286     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7287        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7288     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7289     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7290       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7291       if(capt)
7292       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7293     }
7294   }
7295   DrawPosition(FALSE, NULL);
7296 }
7297
7298 int
7299 Explode (Board board, int fromX, int fromY, int toX, int toY)
7300 {
7301     if(gameInfo.variant == VariantAtomic &&
7302        (board[toY][toX] != EmptySquare ||                     // capture?
7303         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7304                          board[fromY][fromX] == BlackPawn   )
7305       )) {
7306         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7307         return TRUE;
7308     }
7309     return FALSE;
7310 }
7311
7312 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7313
7314 int
7315 CanPromote (ChessSquare piece, int y)
7316 {
7317         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7318         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7319         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7320         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7321            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7322            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7323          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7324         return (piece == BlackPawn && y <= zone ||
7325                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7326                 piece == BlackLance && y == 1 ||
7327                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7328 }
7329
7330 void
7331 HoverEvent (int xPix, int yPix, int x, int y)
7332 {
7333         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7334         int r, f;
7335         if(!first.highlight) return;
7336         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7337         if(x == oldX && y == oldY) return; // only do something if we enter new square
7338         oldFromX = fromX; oldFromY = fromY;
7339         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7340           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7341             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7342           hoverSavedValid = 1;
7343         } else if(oldX != x || oldY != y) {
7344           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7345           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7346           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7347             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7348           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7349             char buf[MSG_SIZ];
7350             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7351             SendToProgram(buf, &first);
7352           }
7353           oldX = x; oldY = y;
7354 //        SetHighlights(fromX, fromY, x, y);
7355         }
7356 }
7357
7358 void ReportClick(char *action, int x, int y)
7359 {
7360         char buf[MSG_SIZ]; // Inform engine of what user does
7361         int r, f;
7362         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7363           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7364             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7365         if(!first.highlight || gameMode == EditPosition) return;
7366         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7367         SendToProgram(buf, &first);
7368 }
7369
7370 void
7371 LeftClick (ClickType clickType, int xPix, int yPix)
7372 {
7373     int x, y;
7374     Boolean saveAnimate;
7375     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7376     char promoChoice = NULLCHAR;
7377     ChessSquare piece;
7378     static TimeMark lastClickTime, prevClickTime;
7379
7380     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7381
7382     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7383
7384     if (clickType == Press) ErrorPopDown();
7385     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7386
7387     x = EventToSquare(xPix, BOARD_WIDTH);
7388     y = EventToSquare(yPix, BOARD_HEIGHT);
7389     if (!flipView && y >= 0) {
7390         y = BOARD_HEIGHT - 1 - y;
7391     }
7392     if (flipView && x >= 0) {
7393         x = BOARD_WIDTH - 1 - x;
7394     }
7395
7396     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7397         defaultPromoChoice = promoSweep;
7398         promoSweep = EmptySquare;   // terminate sweep
7399         promoDefaultAltered = TRUE;
7400         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7401     }
7402
7403     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7404         if(clickType == Release) return; // ignore upclick of click-click destination
7405         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7406         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7407         if(gameInfo.holdingsWidth &&
7408                 (WhiteOnMove(currentMove)
7409                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7410                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7411             // click in right holdings, for determining promotion piece
7412             ChessSquare p = boards[currentMove][y][x];
7413             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7414             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7415             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7416                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7417                 fromX = fromY = -1;
7418                 return;
7419             }
7420         }
7421         DrawPosition(FALSE, boards[currentMove]);
7422         return;
7423     }
7424
7425     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7426     if(clickType == Press
7427             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7428               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7429               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7430         return;
7431
7432     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7433         // could be static click on premove from-square: abort premove
7434         gotPremove = 0;
7435         ClearPremoveHighlights();
7436     }
7437
7438     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7439         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7440
7441     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7442         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7443                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7444         defaultPromoChoice = DefaultPromoChoice(side);
7445     }
7446
7447     autoQueen = appData.alwaysPromoteToQueen;
7448
7449     if (fromX == -1) {
7450       int originalY = y;
7451       gatingPiece = EmptySquare;
7452       if (clickType != Press) {
7453         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7454             DragPieceEnd(xPix, yPix); dragging = 0;
7455             DrawPosition(FALSE, NULL);
7456         }
7457         return;
7458       }
7459       doubleClick = FALSE;
7460       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7461         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7462       }
7463       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7464       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7465          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7466          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7467             /* First square */
7468             if (OKToStartUserMove(fromX, fromY)) {
7469                 second = 0;
7470                 ReportClick("lift", x, y);
7471                 MarkTargetSquares(0);
7472                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7473                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7474                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7475                     promoSweep = defaultPromoChoice;
7476                     selectFlag = 0; lastX = xPix; lastY = yPix;
7477                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7478                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7479                 }
7480                 if (appData.highlightDragging) {
7481                     SetHighlights(fromX, fromY, -1, -1);
7482                 } else {
7483                     ClearHighlights();
7484                 }
7485             } else fromX = fromY = -1;
7486             return;
7487         }
7488     }
7489
7490     /* fromX != -1 */
7491     if (clickType == Press && gameMode != EditPosition) {
7492         ChessSquare fromP;
7493         ChessSquare toP;
7494         int frc;
7495
7496         // ignore off-board to clicks
7497         if(y < 0 || x < 0) return;
7498
7499         /* Check if clicking again on the same color piece */
7500         fromP = boards[currentMove][fromY][fromX];
7501         toP = boards[currentMove][y][x];
7502         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7503         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7504            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7505              WhitePawn <= toP && toP <= WhiteKing &&
7506              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7507              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7508             (BlackPawn <= fromP && fromP <= BlackKing &&
7509              BlackPawn <= toP && toP <= BlackKing &&
7510              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7511              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7512             /* Clicked again on same color piece -- changed his mind */
7513             second = (x == fromX && y == fromY);
7514             killX = killY = -1;
7515             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7516                 second = FALSE; // first double-click rather than scond click
7517                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7518             }
7519             promoDefaultAltered = FALSE;
7520             MarkTargetSquares(1);
7521            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7522             if (appData.highlightDragging) {
7523                 SetHighlights(x, y, -1, -1);
7524             } else {
7525                 ClearHighlights();
7526             }
7527             if (OKToStartUserMove(x, y)) {
7528                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7529                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7530                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7531                  gatingPiece = boards[currentMove][fromY][fromX];
7532                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7533                 fromX = x;
7534                 fromY = y; dragging = 1;
7535                 ReportClick("lift", x, y);
7536                 MarkTargetSquares(0);
7537                 DragPieceBegin(xPix, yPix, FALSE);
7538                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7539                     promoSweep = defaultPromoChoice;
7540                     selectFlag = 0; lastX = xPix; lastY = yPix;
7541                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7542                 }
7543             }
7544            }
7545            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7546            second = FALSE;
7547         }
7548         // ignore clicks on holdings
7549         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7550     }
7551
7552     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7553         DragPieceEnd(xPix, yPix); dragging = 0;
7554         if(clearFlag) {
7555             // a deferred attempt to click-click move an empty square on top of a piece
7556             boards[currentMove][y][x] = EmptySquare;
7557             ClearHighlights();
7558             DrawPosition(FALSE, boards[currentMove]);
7559             fromX = fromY = -1; clearFlag = 0;
7560             return;
7561         }
7562         if (appData.animateDragging) {
7563             /* Undo animation damage if any */
7564             DrawPosition(FALSE, NULL);
7565         }
7566         if (second || sweepSelecting) {
7567             /* Second up/down in same square; just abort move */
7568             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7569             second = sweepSelecting = 0;
7570             fromX = fromY = -1;
7571             gatingPiece = EmptySquare;
7572             MarkTargetSquares(1);
7573             ClearHighlights();
7574             gotPremove = 0;
7575             ClearPremoveHighlights();
7576         } else {
7577             /* First upclick in same square; start click-click mode */
7578             SetHighlights(x, y, -1, -1);
7579         }
7580         return;
7581     }
7582
7583     clearFlag = 0;
7584
7585     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7586        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7587         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7588         DisplayMessage(_("only marked squares are legal"),"");
7589         DrawPosition(TRUE, NULL);
7590         return; // ignore to-click
7591     }
7592
7593     /* we now have a different from- and (possibly off-board) to-square */
7594     /* Completed move */
7595     if(!sweepSelecting) {
7596         toX = x;
7597         toY = y;
7598     }
7599
7600     piece = boards[currentMove][fromY][fromX];
7601
7602     saveAnimate = appData.animate;
7603     if (clickType == Press) {
7604         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7605         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7606             // must be Edit Position mode with empty-square selected
7607             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7608             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7609             return;
7610         }
7611         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7612             return;
7613         }
7614         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7615             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7616         } else
7617         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7618         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7619           if(appData.sweepSelect) {
7620             promoSweep = defaultPromoChoice;
7621             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7622             selectFlag = 0; lastX = xPix; lastY = yPix;
7623             Sweep(0); // Pawn that is going to promote: preview promotion piece
7624             sweepSelecting = 1;
7625             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7626             MarkTargetSquares(1);
7627           }
7628           return; // promo popup appears on up-click
7629         }
7630         /* Finish clickclick move */
7631         if (appData.animate || appData.highlightLastMove) {
7632             SetHighlights(fromX, fromY, toX, toY);
7633         } else {
7634             ClearHighlights();
7635         }
7636     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7637         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7638         if (appData.animate || appData.highlightLastMove) {
7639             SetHighlights(fromX, fromY, toX, toY);
7640         } else {
7641             ClearHighlights();
7642         }
7643     } else {
7644 #if 0
7645 // [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
7646         /* Finish drag move */
7647         if (appData.highlightLastMove) {
7648             SetHighlights(fromX, fromY, toX, toY);
7649         } else {
7650             ClearHighlights();
7651         }
7652 #endif
7653         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7654         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7655           dragging *= 2;            // flag button-less dragging if we are dragging
7656           MarkTargetSquares(1);
7657           if(x == killX && y == killY) killX = killY = -1; else {
7658             killX = x; killY = y;     //remeber this square as intermediate
7659             ReportClick("put", x, y); // and inform engine
7660             ReportClick("lift", x, y);
7661             MarkTargetSquares(0);
7662             return;
7663           }
7664         }
7665         DragPieceEnd(xPix, yPix); dragging = 0;
7666         /* Don't animate move and drag both */
7667         appData.animate = FALSE;
7668     }
7669
7670     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7671     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7672         ChessSquare piece = boards[currentMove][fromY][fromX];
7673         if(gameMode == EditPosition && piece != EmptySquare &&
7674            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7675             int n;
7676
7677             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7678                 n = PieceToNumber(piece - (int)BlackPawn);
7679                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7680                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7681                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7682             } else
7683             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7684                 n = PieceToNumber(piece);
7685                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7686                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7687                 boards[currentMove][n][BOARD_WIDTH-2]++;
7688             }
7689             boards[currentMove][fromY][fromX] = EmptySquare;
7690         }
7691         ClearHighlights();
7692         fromX = fromY = -1;
7693         MarkTargetSquares(1);
7694         DrawPosition(TRUE, boards[currentMove]);
7695         return;
7696     }
7697
7698     // off-board moves should not be highlighted
7699     if(x < 0 || y < 0) ClearHighlights();
7700     else ReportClick("put", x, y);
7701
7702     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7703
7704     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7705         SetHighlights(fromX, fromY, toX, toY);
7706         MarkTargetSquares(1);
7707         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7708             // [HGM] super: promotion to captured piece selected from holdings
7709             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7710             promotionChoice = TRUE;
7711             // kludge follows to temporarily execute move on display, without promoting yet
7712             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7713             boards[currentMove][toY][toX] = p;
7714             DrawPosition(FALSE, boards[currentMove]);
7715             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7716             boards[currentMove][toY][toX] = q;
7717             DisplayMessage("Click in holdings to choose piece", "");
7718             return;
7719         }
7720         PromotionPopUp(promoChoice);
7721     } else {
7722         int oldMove = currentMove;
7723         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7724         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7725         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7726         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7727            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7728             DrawPosition(TRUE, boards[currentMove]);
7729         MarkTargetSquares(1);
7730         fromX = fromY = -1;
7731     }
7732     appData.animate = saveAnimate;
7733     if (appData.animate || appData.animateDragging) {
7734         /* Undo animation damage if needed */
7735         DrawPosition(FALSE, NULL);
7736     }
7737 }
7738
7739 int
7740 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7741 {   // front-end-free part taken out of PieceMenuPopup
7742     int whichMenu; int xSqr, ySqr;
7743
7744     if(seekGraphUp) { // [HGM] seekgraph
7745         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7746         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7747         return -2;
7748     }
7749
7750     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7751          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7752         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7753         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7754         if(action == Press)   {
7755             originalFlip = flipView;
7756             flipView = !flipView; // temporarily flip board to see game from partners perspective
7757             DrawPosition(TRUE, partnerBoard);
7758             DisplayMessage(partnerStatus, "");
7759             partnerUp = TRUE;
7760         } else if(action == Release) {
7761             flipView = originalFlip;
7762             DrawPosition(TRUE, boards[currentMove]);
7763             partnerUp = FALSE;
7764         }
7765         return -2;
7766     }
7767
7768     xSqr = EventToSquare(x, BOARD_WIDTH);
7769     ySqr = EventToSquare(y, BOARD_HEIGHT);
7770     if (action == Release) {
7771         if(pieceSweep != EmptySquare) {
7772             EditPositionMenuEvent(pieceSweep, toX, toY);
7773             pieceSweep = EmptySquare;
7774         } else UnLoadPV(); // [HGM] pv
7775     }
7776     if (action != Press) return -2; // return code to be ignored
7777     switch (gameMode) {
7778       case IcsExamining:
7779         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7780       case EditPosition:
7781         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7782         if (xSqr < 0 || ySqr < 0) return -1;
7783         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7784         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7785         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7786         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7787         NextPiece(0);
7788         return 2; // grab
7789       case IcsObserving:
7790         if(!appData.icsEngineAnalyze) return -1;
7791       case IcsPlayingWhite:
7792       case IcsPlayingBlack:
7793         if(!appData.zippyPlay) goto noZip;
7794       case AnalyzeMode:
7795       case AnalyzeFile:
7796       case MachinePlaysWhite:
7797       case MachinePlaysBlack:
7798       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7799         if (!appData.dropMenu) {
7800           LoadPV(x, y);
7801           return 2; // flag front-end to grab mouse events
7802         }
7803         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7804            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7805       case EditGame:
7806       noZip:
7807         if (xSqr < 0 || ySqr < 0) return -1;
7808         if (!appData.dropMenu || appData.testLegality &&
7809             gameInfo.variant != VariantBughouse &&
7810             gameInfo.variant != VariantCrazyhouse) return -1;
7811         whichMenu = 1; // drop menu
7812         break;
7813       default:
7814         return -1;
7815     }
7816
7817     if (((*fromX = xSqr) < 0) ||
7818         ((*fromY = ySqr) < 0)) {
7819         *fromX = *fromY = -1;
7820         return -1;
7821     }
7822     if (flipView)
7823       *fromX = BOARD_WIDTH - 1 - *fromX;
7824     else
7825       *fromY = BOARD_HEIGHT - 1 - *fromY;
7826
7827     return whichMenu;
7828 }
7829
7830 void
7831 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7832 {
7833 //    char * hint = lastHint;
7834     FrontEndProgramStats stats;
7835
7836     stats.which = cps == &first ? 0 : 1;
7837     stats.depth = cpstats->depth;
7838     stats.nodes = cpstats->nodes;
7839     stats.score = cpstats->score;
7840     stats.time = cpstats->time;
7841     stats.pv = cpstats->movelist;
7842     stats.hint = lastHint;
7843     stats.an_move_index = 0;
7844     stats.an_move_count = 0;
7845
7846     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7847         stats.hint = cpstats->move_name;
7848         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7849         stats.an_move_count = cpstats->nr_moves;
7850     }
7851
7852     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
7853
7854     SetProgramStats( &stats );
7855 }
7856
7857 void
7858 ClearEngineOutputPane (int which)
7859 {
7860     static FrontEndProgramStats dummyStats;
7861     dummyStats.which = which;
7862     dummyStats.pv = "#";
7863     SetProgramStats( &dummyStats );
7864 }
7865
7866 #define MAXPLAYERS 500
7867
7868 char *
7869 TourneyStandings (int display)
7870 {
7871     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7872     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7873     char result, *p, *names[MAXPLAYERS];
7874
7875     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7876         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7877     names[0] = p = strdup(appData.participants);
7878     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7879
7880     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7881
7882     while(result = appData.results[nr]) {
7883         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7884         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7885         wScore = bScore = 0;
7886         switch(result) {
7887           case '+': wScore = 2; break;
7888           case '-': bScore = 2; break;
7889           case '=': wScore = bScore = 1; break;
7890           case ' ':
7891           case '*': return strdup("busy"); // tourney not finished
7892         }
7893         score[w] += wScore;
7894         score[b] += bScore;
7895         games[w]++;
7896         games[b]++;
7897         nr++;
7898     }
7899     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7900     for(w=0; w<nPlayers; w++) {
7901         bScore = -1;
7902         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7903         ranking[w] = b; points[w] = bScore; score[b] = -2;
7904     }
7905     p = malloc(nPlayers*34+1);
7906     for(w=0; w<nPlayers && w<display; w++)
7907         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7908     free(names[0]);
7909     return p;
7910 }
7911
7912 void
7913 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7914 {       // count all piece types
7915         int p, f, r;
7916         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7917         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7918         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7919                 p = board[r][f];
7920                 pCnt[p]++;
7921                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7922                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7923                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7924                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7925                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7926                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7927         }
7928 }
7929
7930 int
7931 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7932 {
7933         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7934         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7935
7936         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7937         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7938         if(myPawns == 2 && nMine == 3) // KPP
7939             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7940         if(myPawns == 1 && nMine == 2) // KP
7941             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7942         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7943             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7944         if(myPawns) return FALSE;
7945         if(pCnt[WhiteRook+side])
7946             return pCnt[BlackRook-side] ||
7947                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7948                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7949                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7950         if(pCnt[WhiteCannon+side]) {
7951             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7952             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7953         }
7954         if(pCnt[WhiteKnight+side])
7955             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7956         return FALSE;
7957 }
7958
7959 int
7960 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7961 {
7962         VariantClass v = gameInfo.variant;
7963
7964         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7965         if(v == VariantShatranj) return TRUE; // always winnable through baring
7966         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7967         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7968
7969         if(v == VariantXiangqi) {
7970                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7971
7972                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7973                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7974                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7975                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7976                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7977                 if(stale) // we have at least one last-rank P plus perhaps C
7978                     return majors // KPKX
7979                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7980                 else // KCA*E*
7981                     return pCnt[WhiteFerz+side] // KCAK
7982                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7983                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7984                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7985
7986         } else if(v == VariantKnightmate) {
7987                 if(nMine == 1) return FALSE;
7988                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7989         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7990                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7991
7992                 if(nMine == 1) return FALSE; // bare King
7993                 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
7994                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7995                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7996                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7997                 if(pCnt[WhiteKnight+side])
7998                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7999                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8000                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8001                 if(nBishops)
8002                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8003                 if(pCnt[WhiteAlfil+side])
8004                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8005                 if(pCnt[WhiteWazir+side])
8006                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8007         }
8008
8009         return TRUE;
8010 }
8011
8012 int
8013 CompareWithRights (Board b1, Board b2)
8014 {
8015     int rights = 0;
8016     if(!CompareBoards(b1, b2)) return FALSE;
8017     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8018     /* compare castling rights */
8019     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8020            rights++; /* King lost rights, while rook still had them */
8021     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8022         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8023            rights++; /* but at least one rook lost them */
8024     }
8025     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8026            rights++;
8027     if( b1[CASTLING][5] != NoRights ) {
8028         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8029            rights++;
8030     }
8031     return rights == 0;
8032 }
8033
8034 int
8035 Adjudicate (ChessProgramState *cps)
8036 {       // [HGM] some adjudications useful with buggy engines
8037         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8038         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8039         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8040         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8041         int k, drop, count = 0; static int bare = 1;
8042         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8043         Boolean canAdjudicate = !appData.icsActive;
8044
8045         // most tests only when we understand the game, i.e. legality-checking on
8046             if( appData.testLegality )
8047             {   /* [HGM] Some more adjudications for obstinate engines */
8048                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8049                 static int moveCount = 6;
8050                 ChessMove result;
8051                 char *reason = NULL;
8052
8053                 /* Count what is on board. */
8054                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8055
8056                 /* Some material-based adjudications that have to be made before stalemate test */
8057                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8058                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8059                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8060                      if(canAdjudicate && appData.checkMates) {
8061                          if(engineOpponent)
8062                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8063                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8064                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8065                          return 1;
8066                      }
8067                 }
8068
8069                 /* Bare King in Shatranj (loses) or Losers (wins) */
8070                 if( nrW == 1 || nrB == 1) {
8071                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8072                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8073                      if(canAdjudicate && appData.checkMates) {
8074                          if(engineOpponent)
8075                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8076                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8077                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8078                          return 1;
8079                      }
8080                   } else
8081                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8082                   {    /* bare King */
8083                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8084                         if(canAdjudicate && appData.checkMates) {
8085                             /* but only adjudicate if adjudication enabled */
8086                             if(engineOpponent)
8087                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8088                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8089                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8090                             return 1;
8091                         }
8092                   }
8093                 } else bare = 1;
8094
8095
8096             // don't wait for engine to announce game end if we can judge ourselves
8097             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8098               case MT_CHECK:
8099                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8100                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8101                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8102                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8103                             checkCnt++;
8104                         if(checkCnt >= 2) {
8105                             reason = "Xboard adjudication: 3rd check";
8106                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8107                             break;
8108                         }
8109                     }
8110                 }
8111               case MT_NONE:
8112               default:
8113                 break;
8114               case MT_STEALMATE:
8115               case MT_STALEMATE:
8116               case MT_STAINMATE:
8117                 reason = "Xboard adjudication: Stalemate";
8118                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8119                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8120                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8121                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8122                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8123                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8124                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8125                                                                         EP_CHECKMATE : EP_WINS);
8126                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8127                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8128                 }
8129                 break;
8130               case MT_CHECKMATE:
8131                 reason = "Xboard adjudication: Checkmate";
8132                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8133                 if(gameInfo.variant == VariantShogi) {
8134                     if(forwardMostMove > backwardMostMove
8135                        && moveList[forwardMostMove-1][1] == '@'
8136                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8137                         reason = "XBoard adjudication: pawn-drop mate";
8138                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8139                     }
8140                 }
8141                 break;
8142             }
8143
8144                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8145                     case EP_STALEMATE:
8146                         result = GameIsDrawn; break;
8147                     case EP_CHECKMATE:
8148                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8149                     case EP_WINS:
8150                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8151                     default:
8152                         result = EndOfFile;
8153                 }
8154                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8155                     if(engineOpponent)
8156                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8157                     GameEnds( result, reason, GE_XBOARD );
8158                     return 1;
8159                 }
8160
8161                 /* Next absolutely insufficient mating material. */
8162                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8163                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8164                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8165
8166                      /* always flag draws, for judging claims */
8167                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8168
8169                      if(canAdjudicate && appData.materialDraws) {
8170                          /* but only adjudicate them if adjudication enabled */
8171                          if(engineOpponent) {
8172                            SendToProgram("force\n", engineOpponent); // suppress reply
8173                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8174                          }
8175                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8176                          return 1;
8177                      }
8178                 }
8179
8180                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8181                 if(gameInfo.variant == VariantXiangqi ?
8182                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8183                  : nrW + nrB == 4 &&
8184                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8185                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8186                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8187                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8188                    ) ) {
8189                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8190                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8191                           if(engineOpponent) {
8192                             SendToProgram("force\n", engineOpponent); // suppress reply
8193                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8194                           }
8195                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8196                           return 1;
8197                      }
8198                 } else moveCount = 6;
8199             }
8200
8201         // Repetition draws and 50-move rule can be applied independently of legality testing
8202
8203                 /* Check for rep-draws */
8204                 count = 0;
8205                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8206                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8207                 for(k = forwardMostMove-2;
8208                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8209                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8210                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8211                     k-=2)
8212                 {   int rights=0;
8213                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8214                         /* compare castling rights */
8215                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8216                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8217                                 rights++; /* King lost rights, while rook still had them */
8218                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8219                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8220                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8221                                    rights++; /* but at least one rook lost them */
8222                         }
8223                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8224                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8225                                 rights++;
8226                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8227                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8228                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8229                                    rights++;
8230                         }
8231                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8232                             && appData.drawRepeats > 1) {
8233                              /* adjudicate after user-specified nr of repeats */
8234                              int result = GameIsDrawn;
8235                              char *details = "XBoard adjudication: repetition draw";
8236                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8237                                 // [HGM] xiangqi: check for forbidden perpetuals
8238                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8239                                 for(m=forwardMostMove; m>k; m-=2) {
8240                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8241                                         ourPerpetual = 0; // the current mover did not always check
8242                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8243                                         hisPerpetual = 0; // the opponent did not always check
8244                                 }
8245                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8246                                                                         ourPerpetual, hisPerpetual);
8247                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8248                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8249                                     details = "Xboard adjudication: perpetual checking";
8250                                 } else
8251                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8252                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8253                                 } else
8254                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8255                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8256                                         result = BlackWins;
8257                                         details = "Xboard adjudication: repetition";
8258                                     }
8259                                 } else // it must be XQ
8260                                 // Now check for perpetual chases
8261                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8262                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8263                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8264                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8265                                         static char resdet[MSG_SIZ];
8266                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8267                                         details = resdet;
8268                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8269                                     } else
8270                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8271                                         break; // Abort repetition-checking loop.
8272                                 }
8273                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8274                              }
8275                              if(engineOpponent) {
8276                                SendToProgram("force\n", engineOpponent); // suppress reply
8277                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8278                              }
8279                              GameEnds( result, details, GE_XBOARD );
8280                              return 1;
8281                         }
8282                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8283                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8284                     }
8285                 }
8286
8287                 /* Now we test for 50-move draws. Determine ply count */
8288                 count = forwardMostMove;
8289                 /* look for last irreversble move */
8290                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8291                     count--;
8292                 /* if we hit starting position, add initial plies */
8293                 if( count == backwardMostMove )
8294                     count -= initialRulePlies;
8295                 count = forwardMostMove - count;
8296                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8297                         // adjust reversible move counter for checks in Xiangqi
8298                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8299                         if(i < backwardMostMove) i = backwardMostMove;
8300                         while(i <= forwardMostMove) {
8301                                 lastCheck = inCheck; // check evasion does not count
8302                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8303                                 if(inCheck || lastCheck) count--; // check does not count
8304                                 i++;
8305                         }
8306                 }
8307                 if( count >= 100)
8308                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8309                          /* this is used to judge if draw claims are legal */
8310                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8311                          if(engineOpponent) {
8312                            SendToProgram("force\n", engineOpponent); // suppress reply
8313                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8314                          }
8315                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8316                          return 1;
8317                 }
8318
8319                 /* if draw offer is pending, treat it as a draw claim
8320                  * when draw condition present, to allow engines a way to
8321                  * claim draws before making their move to avoid a race
8322                  * condition occurring after their move
8323                  */
8324                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8325                          char *p = NULL;
8326                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8327                              p = "Draw claim: 50-move rule";
8328                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8329                              p = "Draw claim: 3-fold repetition";
8330                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8331                              p = "Draw claim: insufficient mating material";
8332                          if( p != NULL && canAdjudicate) {
8333                              if(engineOpponent) {
8334                                SendToProgram("force\n", engineOpponent); // suppress reply
8335                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8336                              }
8337                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8338                              return 1;
8339                          }
8340                 }
8341
8342                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8343                     if(engineOpponent) {
8344                       SendToProgram("force\n", engineOpponent); // suppress reply
8345                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8346                     }
8347                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8348                     return 1;
8349                 }
8350         return 0;
8351 }
8352
8353 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8354 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8355 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8356
8357 static int
8358 BitbaseProbe ()
8359 {
8360     int pieces[10], squares[10], cnt=0, r, f, res;
8361     static int loaded;
8362     static PPROBE_EGBB probeBB;
8363     if(!appData.testLegality) return 10;
8364     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8365     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8366     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8367     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8368         ChessSquare piece = boards[forwardMostMove][r][f];
8369         int black = (piece >= BlackPawn);
8370         int type = piece - black*BlackPawn;
8371         if(piece == EmptySquare) continue;
8372         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8373         if(type == WhiteKing) type = WhiteQueen + 1;
8374         type = egbbCode[type];
8375         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8376         pieces[cnt] = type + black*6;
8377         if(++cnt > 5) return 11;
8378     }
8379     pieces[cnt] = squares[cnt] = 0;
8380     // probe EGBB
8381     if(loaded == 2) return 13; // loading failed before
8382     if(loaded == 0) {
8383         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8384         HMODULE lib;
8385         PLOAD_EGBB loadBB;
8386         loaded = 2; // prepare for failure
8387         if(!path) return 13; // no egbb installed
8388         strncpy(buf, path + 8, MSG_SIZ);
8389         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8390         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8391         lib = LoadLibrary(buf);
8392         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8393         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8394         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8395         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8396         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8397         loaded = 1; // success!
8398     }
8399     res = probeBB(forwardMostMove & 1, pieces, squares);
8400     return res > 0 ? 1 : res < 0 ? -1 : 0;
8401 }
8402
8403 char *
8404 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8405 {   // [HGM] book: this routine intercepts moves to simulate book replies
8406     char *bookHit = NULL;
8407
8408     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8409         char buf[MSG_SIZ];
8410         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8411         SendToProgram(buf, cps);
8412     }
8413     //first determine if the incoming move brings opponent into his book
8414     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8415         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8416     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8417     if(bookHit != NULL && !cps->bookSuspend) {
8418         // make sure opponent is not going to reply after receiving move to book position
8419         SendToProgram("force\n", cps);
8420         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8421     }
8422     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8423     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8424     // now arrange restart after book miss
8425     if(bookHit) {
8426         // after a book hit we never send 'go', and the code after the call to this routine
8427         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8428         char buf[MSG_SIZ], *move = bookHit;
8429         if(cps->useSAN) {
8430             int fromX, fromY, toX, toY;
8431             char promoChar;
8432             ChessMove moveType;
8433             move = buf + 30;
8434             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8435                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8436                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8437                                     PosFlags(forwardMostMove),
8438                                     fromY, fromX, toY, toX, promoChar, move);
8439             } else {
8440                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8441                 bookHit = NULL;
8442             }
8443         }
8444         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8445         SendToProgram(buf, cps);
8446         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8447     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8448         SendToProgram("go\n", cps);
8449         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8450     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8451         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8452             SendToProgram("go\n", cps);
8453         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8454     }
8455     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8456 }
8457
8458 int
8459 LoadError (char *errmess, ChessProgramState *cps)
8460 {   // unloads engine and switches back to -ncp mode if it was first
8461     if(cps->initDone) return FALSE;
8462     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8463     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8464     cps->pr = NoProc;
8465     if(cps == &first) {
8466         appData.noChessProgram = TRUE;
8467         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8468         gameMode = BeginningOfGame; ModeHighlight();
8469         SetNCPMode();
8470     }
8471     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8472     DisplayMessage("", ""); // erase waiting message
8473     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8474     return TRUE;
8475 }
8476
8477 char *savedMessage;
8478 ChessProgramState *savedState;
8479 void
8480 DeferredBookMove (void)
8481 {
8482         if(savedState->lastPing != savedState->lastPong)
8483                     ScheduleDelayedEvent(DeferredBookMove, 10);
8484         else
8485         HandleMachineMove(savedMessage, savedState);
8486 }
8487
8488 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8489 static ChessProgramState *stalledEngine;
8490 static char stashedInputMove[MSG_SIZ];
8491
8492 void
8493 HandleMachineMove (char *message, ChessProgramState *cps)
8494 {
8495     static char firstLeg[20];
8496     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8497     char realname[MSG_SIZ];
8498     int fromX, fromY, toX, toY;
8499     ChessMove moveType;
8500     char promoChar, roar;
8501     char *p, *pv=buf1;
8502     int machineWhite, oldError;
8503     char *bookHit;
8504
8505     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8506         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8507         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8508             DisplayError(_("Invalid pairing from pairing engine"), 0);
8509             return;
8510         }
8511         pairingReceived = 1;
8512         NextMatchGame();
8513         return; // Skim the pairing messages here.
8514     }
8515
8516     oldError = cps->userError; cps->userError = 0;
8517
8518 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8519     /*
8520      * Kludge to ignore BEL characters
8521      */
8522     while (*message == '\007') message++;
8523
8524     /*
8525      * [HGM] engine debug message: ignore lines starting with '#' character
8526      */
8527     if(cps->debug && *message == '#') return;
8528
8529     /*
8530      * Look for book output
8531      */
8532     if (cps == &first && bookRequested) {
8533         if (message[0] == '\t' || message[0] == ' ') {
8534             /* Part of the book output is here; append it */
8535             strcat(bookOutput, message);
8536             strcat(bookOutput, "  \n");
8537             return;
8538         } else if (bookOutput[0] != NULLCHAR) {
8539             /* All of book output has arrived; display it */
8540             char *p = bookOutput;
8541             while (*p != NULLCHAR) {
8542                 if (*p == '\t') *p = ' ';
8543                 p++;
8544             }
8545             DisplayInformation(bookOutput);
8546             bookRequested = FALSE;
8547             /* Fall through to parse the current output */
8548         }
8549     }
8550
8551     /*
8552      * Look for machine move.
8553      */
8554     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8555         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8556     {
8557         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8558             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8559             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8560             stalledEngine = cps;
8561             if(appData.ponderNextMove) { // bring opponent out of ponder
8562                 if(gameMode == TwoMachinesPlay) {
8563                     if(cps->other->pause)
8564                         PauseEngine(cps->other);
8565                     else
8566                         SendToProgram("easy\n", cps->other);
8567                 }
8568             }
8569             StopClocks();
8570             return;
8571         }
8572
8573         /* This method is only useful on engines that support ping */
8574         if (cps->lastPing != cps->lastPong) {
8575           if (gameMode == BeginningOfGame) {
8576             /* Extra move from before last new; ignore */
8577             if (appData.debugMode) {
8578                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8579             }
8580           } else {
8581             if (appData.debugMode) {
8582                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8583                         cps->which, gameMode);
8584             }
8585
8586             SendToProgram("undo\n", cps);
8587           }
8588           return;
8589         }
8590
8591         switch (gameMode) {
8592           case BeginningOfGame:
8593             /* Extra move from before last reset; ignore */
8594             if (appData.debugMode) {
8595                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8596             }
8597             return;
8598
8599           case EndOfGame:
8600           case IcsIdle:
8601           default:
8602             /* Extra move after we tried to stop.  The mode test is
8603                not a reliable way of detecting this problem, but it's
8604                the best we can do on engines that don't support ping.
8605             */
8606             if (appData.debugMode) {
8607                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8608                         cps->which, gameMode);
8609             }
8610             SendToProgram("undo\n", cps);
8611             return;
8612
8613           case MachinePlaysWhite:
8614           case IcsPlayingWhite:
8615             machineWhite = TRUE;
8616             break;
8617
8618           case MachinePlaysBlack:
8619           case IcsPlayingBlack:
8620             machineWhite = FALSE;
8621             break;
8622
8623           case TwoMachinesPlay:
8624             machineWhite = (cps->twoMachinesColor[0] == 'w');
8625             break;
8626         }
8627         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8628             if (appData.debugMode) {
8629                 fprintf(debugFP,
8630                         "Ignoring move out of turn by %s, gameMode %d"
8631                         ", forwardMost %d\n",
8632                         cps->which, gameMode, forwardMostMove);
8633             }
8634             return;
8635         }
8636
8637         if(cps->alphaRank) AlphaRank(machineMove, 4);
8638
8639         // [HGM] lion: (some very limited) support for Alien protocol
8640         killX = killY = -1;
8641         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8642             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8643             return;
8644         } else if(firstLeg[0]) { // there was a previous leg;
8645             // only support case where same piece makes two step (and don't even test that!)
8646             char buf[20], *p = machineMove+1, *q = buf+1, f;
8647             safeStrCpy(buf, machineMove, 20);
8648             while(isdigit(*q)) q++; // find start of to-square
8649             safeStrCpy(machineMove, firstLeg, 20);
8650             while(isdigit(*p)) p++;
8651             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8652             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8653             firstLeg[0] = NULLCHAR;
8654         }
8655
8656         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8657                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8658             /* Machine move could not be parsed; ignore it. */
8659           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8660                     machineMove, _(cps->which));
8661             DisplayMoveError(buf1);
8662             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8663                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8664             if (gameMode == TwoMachinesPlay) {
8665               GameEnds(machineWhite ? BlackWins : WhiteWins,
8666                        buf1, GE_XBOARD);
8667             }
8668             return;
8669         }
8670
8671         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8672         /* So we have to redo legality test with true e.p. status here,  */
8673         /* to make sure an illegal e.p. capture does not slip through,   */
8674         /* to cause a forfeit on a justified illegal-move complaint      */
8675         /* of the opponent.                                              */
8676         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8677            ChessMove moveType;
8678            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8679                              fromY, fromX, toY, toX, promoChar);
8680             if(moveType == IllegalMove) {
8681               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8682                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8683                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8684                            buf1, GE_XBOARD);
8685                 return;
8686            } else if(!appData.fischerCastling)
8687            /* [HGM] Kludge to handle engines that send FRC-style castling
8688               when they shouldn't (like TSCP-Gothic) */
8689            switch(moveType) {
8690              case WhiteASideCastleFR:
8691              case BlackASideCastleFR:
8692                toX+=2;
8693                currentMoveString[2]++;
8694                break;
8695              case WhiteHSideCastleFR:
8696              case BlackHSideCastleFR:
8697                toX--;
8698                currentMoveString[2]--;
8699                break;
8700              default: ; // nothing to do, but suppresses warning of pedantic compilers
8701            }
8702         }
8703         hintRequested = FALSE;
8704         lastHint[0] = NULLCHAR;
8705         bookRequested = FALSE;
8706         /* Program may be pondering now */
8707         cps->maybeThinking = TRUE;
8708         if (cps->sendTime == 2) cps->sendTime = 1;
8709         if (cps->offeredDraw) cps->offeredDraw--;
8710
8711         /* [AS] Save move info*/
8712         pvInfoList[ forwardMostMove ].score = programStats.score;
8713         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8714         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8715
8716         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8717
8718         /* Test suites abort the 'game' after one move */
8719         if(*appData.finger) {
8720            static FILE *f;
8721            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8722            if(!f) f = fopen(appData.finger, "w");
8723            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8724            else { DisplayFatalError("Bad output file", errno, 0); return; }
8725            free(fen);
8726            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8727         }
8728
8729         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8730         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8731             int count = 0;
8732
8733             while( count < adjudicateLossPlies ) {
8734                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8735
8736                 if( count & 1 ) {
8737                     score = -score; /* Flip score for winning side */
8738                 }
8739
8740                 if( score > appData.adjudicateLossThreshold ) {
8741                     break;
8742                 }
8743
8744                 count++;
8745             }
8746
8747             if( count >= adjudicateLossPlies ) {
8748                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8749
8750                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8751                     "Xboard adjudication",
8752                     GE_XBOARD );
8753
8754                 return;
8755             }
8756         }
8757
8758         if(Adjudicate(cps)) {
8759             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8760             return; // [HGM] adjudicate: for all automatic game ends
8761         }
8762
8763 #if ZIPPY
8764         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8765             first.initDone) {
8766           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8767                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8768                 SendToICS("draw ");
8769                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8770           }
8771           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8772           ics_user_moved = 1;
8773           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8774                 char buf[3*MSG_SIZ];
8775
8776                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8777                         programStats.score / 100.,
8778                         programStats.depth,
8779                         programStats.time / 100.,
8780                         (unsigned int)programStats.nodes,
8781                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8782                         programStats.movelist);
8783                 SendToICS(buf);
8784           }
8785         }
8786 #endif
8787
8788         /* [AS] Clear stats for next move */
8789         ClearProgramStats();
8790         thinkOutput[0] = NULLCHAR;
8791         hiddenThinkOutputState = 0;
8792
8793         bookHit = NULL;
8794         if (gameMode == TwoMachinesPlay) {
8795             /* [HGM] relaying draw offers moved to after reception of move */
8796             /* and interpreting offer as claim if it brings draw condition */
8797             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8798                 SendToProgram("draw\n", cps->other);
8799             }
8800             if (cps->other->sendTime) {
8801                 SendTimeRemaining(cps->other,
8802                                   cps->other->twoMachinesColor[0] == 'w');
8803             }
8804             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8805             if (firstMove && !bookHit) {
8806                 firstMove = FALSE;
8807                 if (cps->other->useColors) {
8808                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8809                 }
8810                 SendToProgram("go\n", cps->other);
8811             }
8812             cps->other->maybeThinking = TRUE;
8813         }
8814
8815         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8816
8817         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8818
8819         if (!pausing && appData.ringBellAfterMoves) {
8820             if(!roar) RingBell();
8821         }
8822
8823         /*
8824          * Reenable menu items that were disabled while
8825          * machine was thinking
8826          */
8827         if (gameMode != TwoMachinesPlay)
8828             SetUserThinkingEnables();
8829
8830         // [HGM] book: after book hit opponent has received move and is now in force mode
8831         // force the book reply into it, and then fake that it outputted this move by jumping
8832         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8833         if(bookHit) {
8834                 static char bookMove[MSG_SIZ]; // a bit generous?
8835
8836                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8837                 strcat(bookMove, bookHit);
8838                 message = bookMove;
8839                 cps = cps->other;
8840                 programStats.nodes = programStats.depth = programStats.time =
8841                 programStats.score = programStats.got_only_move = 0;
8842                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8843
8844                 if(cps->lastPing != cps->lastPong) {
8845                     savedMessage = message; // args for deferred call
8846                     savedState = cps;
8847                     ScheduleDelayedEvent(DeferredBookMove, 10);
8848                     return;
8849                 }
8850                 goto FakeBookMove;
8851         }
8852
8853         return;
8854     }
8855
8856     /* Set special modes for chess engines.  Later something general
8857      *  could be added here; for now there is just one kludge feature,
8858      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8859      *  when "xboard" is given as an interactive command.
8860      */
8861     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8862         cps->useSigint = FALSE;
8863         cps->useSigterm = FALSE;
8864     }
8865     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8866       ParseFeatures(message+8, cps);
8867       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8868     }
8869
8870     if (!strncmp(message, "setup ", 6) && 
8871         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8872           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8873                                         ) { // [HGM] allow first engine to define opening position
8874       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8875       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8876       *buf = NULLCHAR;
8877       if(sscanf(message, "setup (%s", buf) == 1) {
8878         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8879         ASSIGN(appData.pieceToCharTable, buf);
8880       }
8881       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8882       if(dummy >= 3) {
8883         while(message[s] && message[s++] != ' ');
8884         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8885            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8886             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8887             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8888           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8889           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8890           startedFromSetupPosition = FALSE;
8891         }
8892       }
8893       if(startedFromSetupPosition) return;
8894       ParseFEN(boards[0], &dummy, message+s, FALSE);
8895       DrawPosition(TRUE, boards[0]);
8896       CopyBoard(initialPosition, boards[0]);
8897       startedFromSetupPosition = TRUE;
8898       return;
8899     }
8900     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
8901       ChessSquare piece = WhitePawn;
8902       char *p=buf2;
8903       if(*p == '+') piece = CHUPROMOTED WhitePawn, p++;
8904       piece += CharToPiece(*p) - WhitePawn;
8905       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
8906       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
8907       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
8908       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
8909       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
8910       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
8911                                                && gameInfo.variant != VariantGreat
8912                                                && gameInfo.variant != VariantFairy    ) return;
8913       if(piece < EmptySquare) {
8914         pieceDefs = TRUE;
8915         ASSIGN(pieceDesc[piece], buf1);
8916         if(isupper(*p) && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
8917       }
8918       return;
8919     }
8920     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8921      * want this, I was asked to put it in, and obliged.
8922      */
8923     if (!strncmp(message, "setboard ", 9)) {
8924         Board initial_position;
8925
8926         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8927
8928         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8929             DisplayError(_("Bad FEN received from engine"), 0);
8930             return ;
8931         } else {
8932            Reset(TRUE, FALSE);
8933            CopyBoard(boards[0], initial_position);
8934            initialRulePlies = FENrulePlies;
8935            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8936            else gameMode = MachinePlaysBlack;
8937            DrawPosition(FALSE, boards[currentMove]);
8938         }
8939         return;
8940     }
8941
8942     /*
8943      * Look for communication commands
8944      */
8945     if (!strncmp(message, "telluser ", 9)) {
8946         if(message[9] == '\\' && message[10] == '\\')
8947             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8948         PlayTellSound();
8949         DisplayNote(message + 9);
8950         return;
8951     }
8952     if (!strncmp(message, "tellusererror ", 14)) {
8953         cps->userError = 1;
8954         if(message[14] == '\\' && message[15] == '\\')
8955             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8956         PlayTellSound();
8957         DisplayError(message + 14, 0);
8958         return;
8959     }
8960     if (!strncmp(message, "tellopponent ", 13)) {
8961       if (appData.icsActive) {
8962         if (loggedOn) {
8963           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8964           SendToICS(buf1);
8965         }
8966       } else {
8967         DisplayNote(message + 13);
8968       }
8969       return;
8970     }
8971     if (!strncmp(message, "tellothers ", 11)) {
8972       if (appData.icsActive) {
8973         if (loggedOn) {
8974           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8975           SendToICS(buf1);
8976         }
8977       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8978       return;
8979     }
8980     if (!strncmp(message, "tellall ", 8)) {
8981       if (appData.icsActive) {
8982         if (loggedOn) {
8983           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8984           SendToICS(buf1);
8985         }
8986       } else {
8987         DisplayNote(message + 8);
8988       }
8989       return;
8990     }
8991     if (strncmp(message, "warning", 7) == 0) {
8992         /* Undocumented feature, use tellusererror in new code */
8993         DisplayError(message, 0);
8994         return;
8995     }
8996     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8997         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8998         strcat(realname, " query");
8999         AskQuestion(realname, buf2, buf1, cps->pr);
9000         return;
9001     }
9002     /* Commands from the engine directly to ICS.  We don't allow these to be
9003      *  sent until we are logged on. Crafty kibitzes have been known to
9004      *  interfere with the login process.
9005      */
9006     if (loggedOn) {
9007         if (!strncmp(message, "tellics ", 8)) {
9008             SendToICS(message + 8);
9009             SendToICS("\n");
9010             return;
9011         }
9012         if (!strncmp(message, "tellicsnoalias ", 15)) {
9013             SendToICS(ics_prefix);
9014             SendToICS(message + 15);
9015             SendToICS("\n");
9016             return;
9017         }
9018         /* The following are for backward compatibility only */
9019         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9020             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9021             SendToICS(ics_prefix);
9022             SendToICS(message);
9023             SendToICS("\n");
9024             return;
9025         }
9026     }
9027     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9028         if(initPing == cps->lastPong) {
9029             if(gameInfo.variant == VariantUnknown) {
9030                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9031                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9032                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9033             }
9034             initPing = -1;
9035         }
9036         return;
9037     }
9038     if(!strncmp(message, "highlight ", 10)) {
9039         if(appData.testLegality && appData.markers) return;
9040         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9041         return;
9042     }
9043     if(!strncmp(message, "click ", 6)) {
9044         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9045         if(appData.testLegality || !appData.oneClick) return;
9046         sscanf(message+6, "%c%d%c", &f, &y, &c);
9047         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9048         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9049         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9050         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9051         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9052         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9053             LeftClick(Release, lastLeftX, lastLeftY);
9054         controlKey  = (c == ',');
9055         LeftClick(Press, x, y);
9056         LeftClick(Release, x, y);
9057         first.highlight = f;
9058         return;
9059     }
9060     /*
9061      * If the move is illegal, cancel it and redraw the board.
9062      * Also deal with other error cases.  Matching is rather loose
9063      * here to accommodate engines written before the spec.
9064      */
9065     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9066         strncmp(message, "Error", 5) == 0) {
9067         if (StrStr(message, "name") ||
9068             StrStr(message, "rating") || StrStr(message, "?") ||
9069             StrStr(message, "result") || StrStr(message, "board") ||
9070             StrStr(message, "bk") || StrStr(message, "computer") ||
9071             StrStr(message, "variant") || StrStr(message, "hint") ||
9072             StrStr(message, "random") || StrStr(message, "depth") ||
9073             StrStr(message, "accepted")) {
9074             return;
9075         }
9076         if (StrStr(message, "protover")) {
9077           /* Program is responding to input, so it's apparently done
9078              initializing, and this error message indicates it is
9079              protocol version 1.  So we don't need to wait any longer
9080              for it to initialize and send feature commands. */
9081           FeatureDone(cps, 1);
9082           cps->protocolVersion = 1;
9083           return;
9084         }
9085         cps->maybeThinking = FALSE;
9086
9087         if (StrStr(message, "draw")) {
9088             /* Program doesn't have "draw" command */
9089             cps->sendDrawOffers = 0;
9090             return;
9091         }
9092         if (cps->sendTime != 1 &&
9093             (StrStr(message, "time") || StrStr(message, "otim"))) {
9094           /* Program apparently doesn't have "time" or "otim" command */
9095           cps->sendTime = 0;
9096           return;
9097         }
9098         if (StrStr(message, "analyze")) {
9099             cps->analysisSupport = FALSE;
9100             cps->analyzing = FALSE;
9101 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9102             EditGameEvent(); // [HGM] try to preserve loaded game
9103             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9104             DisplayError(buf2, 0);
9105             return;
9106         }
9107         if (StrStr(message, "(no matching move)st")) {
9108           /* Special kludge for GNU Chess 4 only */
9109           cps->stKludge = TRUE;
9110           SendTimeControl(cps, movesPerSession, timeControl,
9111                           timeIncrement, appData.searchDepth,
9112                           searchTime);
9113           return;
9114         }
9115         if (StrStr(message, "(no matching move)sd")) {
9116           /* Special kludge for GNU Chess 4 only */
9117           cps->sdKludge = TRUE;
9118           SendTimeControl(cps, movesPerSession, timeControl,
9119                           timeIncrement, appData.searchDepth,
9120                           searchTime);
9121           return;
9122         }
9123         if (!StrStr(message, "llegal")) {
9124             return;
9125         }
9126         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9127             gameMode == IcsIdle) return;
9128         if (forwardMostMove <= backwardMostMove) return;
9129         if (pausing) PauseEvent();
9130       if(appData.forceIllegal) {
9131             // [HGM] illegal: machine refused move; force position after move into it
9132           SendToProgram("force\n", cps);
9133           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9134                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9135                 // when black is to move, while there might be nothing on a2 or black
9136                 // might already have the move. So send the board as if white has the move.
9137                 // But first we must change the stm of the engine, as it refused the last move
9138                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9139                 if(WhiteOnMove(forwardMostMove)) {
9140                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9141                     SendBoard(cps, forwardMostMove); // kludgeless board
9142                 } else {
9143                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9144                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9145                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9146                 }
9147           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9148             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9149                  gameMode == TwoMachinesPlay)
9150               SendToProgram("go\n", cps);
9151             return;
9152       } else
9153         if (gameMode == PlayFromGameFile) {
9154             /* Stop reading this game file */
9155             gameMode = EditGame;
9156             ModeHighlight();
9157         }
9158         /* [HGM] illegal-move claim should forfeit game when Xboard */
9159         /* only passes fully legal moves                            */
9160         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9161             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9162                                 "False illegal-move claim", GE_XBOARD );
9163             return; // do not take back move we tested as valid
9164         }
9165         currentMove = forwardMostMove-1;
9166         DisplayMove(currentMove-1); /* before DisplayMoveError */
9167         SwitchClocks(forwardMostMove-1); // [HGM] race
9168         DisplayBothClocks();
9169         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9170                 parseList[currentMove], _(cps->which));
9171         DisplayMoveError(buf1);
9172         DrawPosition(FALSE, boards[currentMove]);
9173
9174         SetUserThinkingEnables();
9175         return;
9176     }
9177     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9178         /* Program has a broken "time" command that
9179            outputs a string not ending in newline.
9180            Don't use it. */
9181         cps->sendTime = 0;
9182     }
9183     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9184         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9185             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9186     }
9187
9188     /*
9189      * If chess program startup fails, exit with an error message.
9190      * Attempts to recover here are futile. [HGM] Well, we try anyway
9191      */
9192     if ((StrStr(message, "unknown host") != NULL)
9193         || (StrStr(message, "No remote directory") != NULL)
9194         || (StrStr(message, "not found") != NULL)
9195         || (StrStr(message, "No such file") != NULL)
9196         || (StrStr(message, "can't alloc") != NULL)
9197         || (StrStr(message, "Permission denied") != NULL)) {
9198
9199         cps->maybeThinking = FALSE;
9200         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9201                 _(cps->which), cps->program, cps->host, message);
9202         RemoveInputSource(cps->isr);
9203         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9204             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9205             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9206         }
9207         return;
9208     }
9209
9210     /*
9211      * Look for hint output
9212      */
9213     if (sscanf(message, "Hint: %s", buf1) == 1) {
9214         if (cps == &first && hintRequested) {
9215             hintRequested = FALSE;
9216             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9217                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9218                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9219                                     PosFlags(forwardMostMove),
9220                                     fromY, fromX, toY, toX, promoChar, buf1);
9221                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9222                 DisplayInformation(buf2);
9223             } else {
9224                 /* Hint move could not be parsed!? */
9225               snprintf(buf2, sizeof(buf2),
9226                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9227                         buf1, _(cps->which));
9228                 DisplayError(buf2, 0);
9229             }
9230         } else {
9231           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9232         }
9233         return;
9234     }
9235
9236     /*
9237      * Ignore other messages if game is not in progress
9238      */
9239     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9240         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9241
9242     /*
9243      * look for win, lose, draw, or draw offer
9244      */
9245     if (strncmp(message, "1-0", 3) == 0) {
9246         char *p, *q, *r = "";
9247         p = strchr(message, '{');
9248         if (p) {
9249             q = strchr(p, '}');
9250             if (q) {
9251                 *q = NULLCHAR;
9252                 r = p + 1;
9253             }
9254         }
9255         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9256         return;
9257     } else if (strncmp(message, "0-1", 3) == 0) {
9258         char *p, *q, *r = "";
9259         p = strchr(message, '{');
9260         if (p) {
9261             q = strchr(p, '}');
9262             if (q) {
9263                 *q = NULLCHAR;
9264                 r = p + 1;
9265             }
9266         }
9267         /* Kludge for Arasan 4.1 bug */
9268         if (strcmp(r, "Black resigns") == 0) {
9269             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9270             return;
9271         }
9272         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9273         return;
9274     } else if (strncmp(message, "1/2", 3) == 0) {
9275         char *p, *q, *r = "";
9276         p = strchr(message, '{');
9277         if (p) {
9278             q = strchr(p, '}');
9279             if (q) {
9280                 *q = NULLCHAR;
9281                 r = p + 1;
9282             }
9283         }
9284
9285         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9286         return;
9287
9288     } else if (strncmp(message, "White resign", 12) == 0) {
9289         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9290         return;
9291     } else if (strncmp(message, "Black resign", 12) == 0) {
9292         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9293         return;
9294     } else if (strncmp(message, "White matches", 13) == 0 ||
9295                strncmp(message, "Black matches", 13) == 0   ) {
9296         /* [HGM] ignore GNUShogi noises */
9297         return;
9298     } else if (strncmp(message, "White", 5) == 0 &&
9299                message[5] != '(' &&
9300                StrStr(message, "Black") == NULL) {
9301         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9302         return;
9303     } else if (strncmp(message, "Black", 5) == 0 &&
9304                message[5] != '(') {
9305         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9306         return;
9307     } else if (strcmp(message, "resign") == 0 ||
9308                strcmp(message, "computer resigns") == 0) {
9309         switch (gameMode) {
9310           case MachinePlaysBlack:
9311           case IcsPlayingBlack:
9312             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9313             break;
9314           case MachinePlaysWhite:
9315           case IcsPlayingWhite:
9316             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9317             break;
9318           case TwoMachinesPlay:
9319             if (cps->twoMachinesColor[0] == 'w')
9320               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9321             else
9322               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9323             break;
9324           default:
9325             /* can't happen */
9326             break;
9327         }
9328         return;
9329     } else if (strncmp(message, "opponent mates", 14) == 0) {
9330         switch (gameMode) {
9331           case MachinePlaysBlack:
9332           case IcsPlayingBlack:
9333             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9334             break;
9335           case MachinePlaysWhite:
9336           case IcsPlayingWhite:
9337             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9338             break;
9339           case TwoMachinesPlay:
9340             if (cps->twoMachinesColor[0] == 'w')
9341               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9342             else
9343               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9344             break;
9345           default:
9346             /* can't happen */
9347             break;
9348         }
9349         return;
9350     } else if (strncmp(message, "computer mates", 14) == 0) {
9351         switch (gameMode) {
9352           case MachinePlaysBlack:
9353           case IcsPlayingBlack:
9354             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9355             break;
9356           case MachinePlaysWhite:
9357           case IcsPlayingWhite:
9358             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9359             break;
9360           case TwoMachinesPlay:
9361             if (cps->twoMachinesColor[0] == 'w')
9362               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9363             else
9364               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9365             break;
9366           default:
9367             /* can't happen */
9368             break;
9369         }
9370         return;
9371     } else if (strncmp(message, "checkmate", 9) == 0) {
9372         if (WhiteOnMove(forwardMostMove)) {
9373             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9374         } else {
9375             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9376         }
9377         return;
9378     } else if (strstr(message, "Draw") != NULL ||
9379                strstr(message, "game is a draw") != NULL) {
9380         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9381         return;
9382     } else if (strstr(message, "offer") != NULL &&
9383                strstr(message, "draw") != NULL) {
9384 #if ZIPPY
9385         if (appData.zippyPlay && first.initDone) {
9386             /* Relay offer to ICS */
9387             SendToICS(ics_prefix);
9388             SendToICS("draw\n");
9389         }
9390 #endif
9391         cps->offeredDraw = 2; /* valid until this engine moves twice */
9392         if (gameMode == TwoMachinesPlay) {
9393             if (cps->other->offeredDraw) {
9394                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9395             /* [HGM] in two-machine mode we delay relaying draw offer      */
9396             /* until after we also have move, to see if it is really claim */
9397             }
9398         } else if (gameMode == MachinePlaysWhite ||
9399                    gameMode == MachinePlaysBlack) {
9400           if (userOfferedDraw) {
9401             DisplayInformation(_("Machine accepts your draw offer"));
9402             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9403           } else {
9404             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9405           }
9406         }
9407     }
9408
9409
9410     /*
9411      * Look for thinking output
9412      */
9413     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9414           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9415                                 ) {
9416         int plylev, mvleft, mvtot, curscore, time;
9417         char mvname[MOVE_LEN];
9418         u64 nodes; // [DM]
9419         char plyext;
9420         int ignore = FALSE;
9421         int prefixHint = FALSE;
9422         mvname[0] = NULLCHAR;
9423
9424         switch (gameMode) {
9425           case MachinePlaysBlack:
9426           case IcsPlayingBlack:
9427             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9428             break;
9429           case MachinePlaysWhite:
9430           case IcsPlayingWhite:
9431             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9432             break;
9433           case AnalyzeMode:
9434           case AnalyzeFile:
9435             break;
9436           case IcsObserving: /* [DM] icsEngineAnalyze */
9437             if (!appData.icsEngineAnalyze) ignore = TRUE;
9438             break;
9439           case TwoMachinesPlay:
9440             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9441                 ignore = TRUE;
9442             }
9443             break;
9444           default:
9445             ignore = TRUE;
9446             break;
9447         }
9448
9449         if (!ignore) {
9450             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9451             buf1[0] = NULLCHAR;
9452             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9453                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9454
9455                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9456                     nodes += u64Const(0x100000000);
9457
9458                 if (plyext != ' ' && plyext != '\t') {
9459                     time *= 100;
9460                 }
9461
9462                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9463                 if( cps->scoreIsAbsolute &&
9464                     ( gameMode == MachinePlaysBlack ||
9465                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9466                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9467                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9468                      !WhiteOnMove(currentMove)
9469                     ) )
9470                 {
9471                     curscore = -curscore;
9472                 }
9473
9474                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9475
9476                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9477                         char buf[MSG_SIZ];
9478                         FILE *f;
9479                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9480                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9481                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9482                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9483                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9484                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9485                                 fclose(f);
9486                         }
9487                         else
9488                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9489                           DisplayError(_("failed writing PV"), 0);
9490                 }
9491
9492                 tempStats.depth = plylev;
9493                 tempStats.nodes = nodes;
9494                 tempStats.time = time;
9495                 tempStats.score = curscore;
9496                 tempStats.got_only_move = 0;
9497
9498                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9499                         int ticklen;
9500
9501                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9502                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9503                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9504                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9505                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9506                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9507                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9508                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9509                 }
9510
9511                 /* Buffer overflow protection */
9512                 if (pv[0] != NULLCHAR) {
9513                     if (strlen(pv) >= sizeof(tempStats.movelist)
9514                         && appData.debugMode) {
9515                         fprintf(debugFP,
9516                                 "PV is too long; using the first %u bytes.\n",
9517                                 (unsigned) sizeof(tempStats.movelist) - 1);
9518                     }
9519
9520                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9521                 } else {
9522                     sprintf(tempStats.movelist, " no PV\n");
9523                 }
9524
9525                 if (tempStats.seen_stat) {
9526                     tempStats.ok_to_send = 1;
9527                 }
9528
9529                 if (strchr(tempStats.movelist, '(') != NULL) {
9530                     tempStats.line_is_book = 1;
9531                     tempStats.nr_moves = 0;
9532                     tempStats.moves_left = 0;
9533                 } else {
9534                     tempStats.line_is_book = 0;
9535                 }
9536
9537                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9538                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9539
9540                 SendProgramStatsToFrontend( cps, &tempStats );
9541
9542                 /*
9543                     [AS] Protect the thinkOutput buffer from overflow... this
9544                     is only useful if buf1 hasn't overflowed first!
9545                 */
9546                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9547                          plylev,
9548                          (gameMode == TwoMachinesPlay ?
9549                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9550                          ((double) curscore) / 100.0,
9551                          prefixHint ? lastHint : "",
9552                          prefixHint ? " " : "" );
9553
9554                 if( buf1[0] != NULLCHAR ) {
9555                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9556
9557                     if( strlen(pv) > max_len ) {
9558                         if( appData.debugMode) {
9559                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9560                         }
9561                         pv[max_len+1] = '\0';
9562                     }
9563
9564                     strcat( thinkOutput, pv);
9565                 }
9566
9567                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9568                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9569                     DisplayMove(currentMove - 1);
9570                 }
9571                 return;
9572
9573             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9574                 /* crafty (9.25+) says "(only move) <move>"
9575                  * if there is only 1 legal move
9576                  */
9577                 sscanf(p, "(only move) %s", buf1);
9578                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9579                 sprintf(programStats.movelist, "%s (only move)", buf1);
9580                 programStats.depth = 1;
9581                 programStats.nr_moves = 1;
9582                 programStats.moves_left = 1;
9583                 programStats.nodes = 1;
9584                 programStats.time = 1;
9585                 programStats.got_only_move = 1;
9586
9587                 /* Not really, but we also use this member to
9588                    mean "line isn't going to change" (Crafty
9589                    isn't searching, so stats won't change) */
9590                 programStats.line_is_book = 1;
9591
9592                 SendProgramStatsToFrontend( cps, &programStats );
9593
9594                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9595                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9596                     DisplayMove(currentMove - 1);
9597                 }
9598                 return;
9599             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9600                               &time, &nodes, &plylev, &mvleft,
9601                               &mvtot, mvname) >= 5) {
9602                 /* The stat01: line is from Crafty (9.29+) in response
9603                    to the "." command */
9604                 programStats.seen_stat = 1;
9605                 cps->maybeThinking = TRUE;
9606
9607                 if (programStats.got_only_move || !appData.periodicUpdates)
9608                   return;
9609
9610                 programStats.depth = plylev;
9611                 programStats.time = time;
9612                 programStats.nodes = nodes;
9613                 programStats.moves_left = mvleft;
9614                 programStats.nr_moves = mvtot;
9615                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9616                 programStats.ok_to_send = 1;
9617                 programStats.movelist[0] = '\0';
9618
9619                 SendProgramStatsToFrontend( cps, &programStats );
9620
9621                 return;
9622
9623             } else if (strncmp(message,"++",2) == 0) {
9624                 /* Crafty 9.29+ outputs this */
9625                 programStats.got_fail = 2;
9626                 return;
9627
9628             } else if (strncmp(message,"--",2) == 0) {
9629                 /* Crafty 9.29+ outputs this */
9630                 programStats.got_fail = 1;
9631                 return;
9632
9633             } else if (thinkOutput[0] != NULLCHAR &&
9634                        strncmp(message, "    ", 4) == 0) {
9635                 unsigned message_len;
9636
9637                 p = message;
9638                 while (*p && *p == ' ') p++;
9639
9640                 message_len = strlen( p );
9641
9642                 /* [AS] Avoid buffer overflow */
9643                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9644                     strcat(thinkOutput, " ");
9645                     strcat(thinkOutput, p);
9646                 }
9647
9648                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9649                     strcat(programStats.movelist, " ");
9650                     strcat(programStats.movelist, p);
9651                 }
9652
9653                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9654                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9655                     DisplayMove(currentMove - 1);
9656                 }
9657                 return;
9658             }
9659         }
9660         else {
9661             buf1[0] = NULLCHAR;
9662
9663             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9664                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9665             {
9666                 ChessProgramStats cpstats;
9667
9668                 if (plyext != ' ' && plyext != '\t') {
9669                     time *= 100;
9670                 }
9671
9672                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9673                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9674                     curscore = -curscore;
9675                 }
9676
9677                 cpstats.depth = plylev;
9678                 cpstats.nodes = nodes;
9679                 cpstats.time = time;
9680                 cpstats.score = curscore;
9681                 cpstats.got_only_move = 0;
9682                 cpstats.movelist[0] = '\0';
9683
9684                 if (buf1[0] != NULLCHAR) {
9685                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9686                 }
9687
9688                 cpstats.ok_to_send = 0;
9689                 cpstats.line_is_book = 0;
9690                 cpstats.nr_moves = 0;
9691                 cpstats.moves_left = 0;
9692
9693                 SendProgramStatsToFrontend( cps, &cpstats );
9694             }
9695         }
9696     }
9697 }
9698
9699
9700 /* Parse a game score from the character string "game", and
9701    record it as the history of the current game.  The game
9702    score is NOT assumed to start from the standard position.
9703    The display is not updated in any way.
9704    */
9705 void
9706 ParseGameHistory (char *game)
9707 {
9708     ChessMove moveType;
9709     int fromX, fromY, toX, toY, boardIndex;
9710     char promoChar;
9711     char *p, *q;
9712     char buf[MSG_SIZ];
9713
9714     if (appData.debugMode)
9715       fprintf(debugFP, "Parsing game history: %s\n", game);
9716
9717     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9718     gameInfo.site = StrSave(appData.icsHost);
9719     gameInfo.date = PGNDate();
9720     gameInfo.round = StrSave("-");
9721
9722     /* Parse out names of players */
9723     while (*game == ' ') game++;
9724     p = buf;
9725     while (*game != ' ') *p++ = *game++;
9726     *p = NULLCHAR;
9727     gameInfo.white = StrSave(buf);
9728     while (*game == ' ') game++;
9729     p = buf;
9730     while (*game != ' ' && *game != '\n') *p++ = *game++;
9731     *p = NULLCHAR;
9732     gameInfo.black = StrSave(buf);
9733
9734     /* Parse moves */
9735     boardIndex = blackPlaysFirst ? 1 : 0;
9736     yynewstr(game);
9737     for (;;) {
9738         yyboardindex = boardIndex;
9739         moveType = (ChessMove) Myylex();
9740         switch (moveType) {
9741           case IllegalMove:             /* maybe suicide chess, etc. */
9742   if (appData.debugMode) {
9743     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9744     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9745     setbuf(debugFP, NULL);
9746   }
9747           case WhitePromotion:
9748           case BlackPromotion:
9749           case WhiteNonPromotion:
9750           case BlackNonPromotion:
9751           case NormalMove:
9752           case FirstLeg:
9753           case WhiteCapturesEnPassant:
9754           case BlackCapturesEnPassant:
9755           case WhiteKingSideCastle:
9756           case WhiteQueenSideCastle:
9757           case BlackKingSideCastle:
9758           case BlackQueenSideCastle:
9759           case WhiteKingSideCastleWild:
9760           case WhiteQueenSideCastleWild:
9761           case BlackKingSideCastleWild:
9762           case BlackQueenSideCastleWild:
9763           /* PUSH Fabien */
9764           case WhiteHSideCastleFR:
9765           case WhiteASideCastleFR:
9766           case BlackHSideCastleFR:
9767           case BlackASideCastleFR:
9768           /* POP Fabien */
9769             fromX = currentMoveString[0] - AAA;
9770             fromY = currentMoveString[1] - ONE;
9771             toX = currentMoveString[2] - AAA;
9772             toY = currentMoveString[3] - ONE;
9773             promoChar = currentMoveString[4];
9774             break;
9775           case WhiteDrop:
9776           case BlackDrop:
9777             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9778             fromX = moveType == WhiteDrop ?
9779               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9780             (int) CharToPiece(ToLower(currentMoveString[0]));
9781             fromY = DROP_RANK;
9782             toX = currentMoveString[2] - AAA;
9783             toY = currentMoveString[3] - ONE;
9784             promoChar = NULLCHAR;
9785             break;
9786           case AmbiguousMove:
9787             /* bug? */
9788             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9789   if (appData.debugMode) {
9790     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9791     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9792     setbuf(debugFP, NULL);
9793   }
9794             DisplayError(buf, 0);
9795             return;
9796           case ImpossibleMove:
9797             /* bug? */
9798             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9799   if (appData.debugMode) {
9800     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9801     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9802     setbuf(debugFP, NULL);
9803   }
9804             DisplayError(buf, 0);
9805             return;
9806           case EndOfFile:
9807             if (boardIndex < backwardMostMove) {
9808                 /* Oops, gap.  How did that happen? */
9809                 DisplayError(_("Gap in move list"), 0);
9810                 return;
9811             }
9812             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9813             if (boardIndex > forwardMostMove) {
9814                 forwardMostMove = boardIndex;
9815             }
9816             return;
9817           case ElapsedTime:
9818             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9819                 strcat(parseList[boardIndex-1], " ");
9820                 strcat(parseList[boardIndex-1], yy_text);
9821             }
9822             continue;
9823           case Comment:
9824           case PGNTag:
9825           case NAG:
9826           default:
9827             /* ignore */
9828             continue;
9829           case WhiteWins:
9830           case BlackWins:
9831           case GameIsDrawn:
9832           case GameUnfinished:
9833             if (gameMode == IcsExamining) {
9834                 if (boardIndex < backwardMostMove) {
9835                     /* Oops, gap.  How did that happen? */
9836                     return;
9837                 }
9838                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9839                 return;
9840             }
9841             gameInfo.result = moveType;
9842             p = strchr(yy_text, '{');
9843             if (p == NULL) p = strchr(yy_text, '(');
9844             if (p == NULL) {
9845                 p = yy_text;
9846                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9847             } else {
9848                 q = strchr(p, *p == '{' ? '}' : ')');
9849                 if (q != NULL) *q = NULLCHAR;
9850                 p++;
9851             }
9852             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9853             gameInfo.resultDetails = StrSave(p);
9854             continue;
9855         }
9856         if (boardIndex >= forwardMostMove &&
9857             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9858             backwardMostMove = blackPlaysFirst ? 1 : 0;
9859             return;
9860         }
9861         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9862                                  fromY, fromX, toY, toX, promoChar,
9863                                  parseList[boardIndex]);
9864         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9865         /* currentMoveString is set as a side-effect of yylex */
9866         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9867         strcat(moveList[boardIndex], "\n");
9868         boardIndex++;
9869         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9870         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9871           case MT_NONE:
9872           case MT_STALEMATE:
9873           default:
9874             break;
9875           case MT_CHECK:
9876             if(!IS_SHOGI(gameInfo.variant))
9877                 strcat(parseList[boardIndex - 1], "+");
9878             break;
9879           case MT_CHECKMATE:
9880           case MT_STAINMATE:
9881             strcat(parseList[boardIndex - 1], "#");
9882             break;
9883         }
9884     }
9885 }
9886
9887
9888 /* Apply a move to the given board  */
9889 void
9890 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9891 {
9892   ChessSquare captured = board[toY][toX], piece, king; int p, rookX, oldEP = EP_NONE, berolina = 0;
9893   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9894
9895     /* [HGM] compute & store e.p. status and castling rights for new position */
9896     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9897
9898       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9899       oldEP = (signed char)board[EP_STATUS];
9900       board[EP_STATUS] = EP_NONE;
9901       board[EP_FILE] = board[EP_RANK] = 100;
9902
9903   if (fromY == DROP_RANK) {
9904         /* must be first */
9905         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9906             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9907             return;
9908         }
9909         piece = board[toY][toX] = (ChessSquare) fromX;
9910   } else {
9911 //      ChessSquare victim;
9912       int i;
9913
9914       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9915 //           victim = board[killY][killX],
9916            board[killY][killX] = EmptySquare,
9917            board[EP_STATUS] = EP_CAPTURE;
9918
9919       if( board[toY][toX] != EmptySquare ) {
9920            board[EP_STATUS] = EP_CAPTURE;
9921            if( (fromX != toX || fromY != toY) && // not igui!
9922                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9923                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9924                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9925            }
9926       }
9927
9928       piece = board[fromY][fromX];
9929       if( piece == WhiteLance || piece == BlackLance ) {
9930            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
9931                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
9932                else piece += WhiteLance - WhitePawn; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
9933            }
9934       }
9935       if( piece == WhitePawn ) {
9936            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9937                board[EP_STATUS] = EP_PAWN_MOVE;
9938            if( toY-fromY>=2) {
9939                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
9940                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9941                         gameInfo.variant != VariantBerolina || toX < fromX)
9942                       board[EP_STATUS] = toX | berolina;
9943                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9944                         gameInfo.variant != VariantBerolina || toX > fromX)
9945                       board[EP_STATUS] = toX;
9946            }
9947       } else
9948       if( piece == BlackPawn ) {
9949            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9950                board[EP_STATUS] = EP_PAWN_MOVE;
9951            if( toY-fromY<= -2) {
9952                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
9953                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9954                         gameInfo.variant != VariantBerolina || toX < fromX)
9955                       board[EP_STATUS] = toX | berolina;
9956                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9957                         gameInfo.variant != VariantBerolina || toX > fromX)
9958                       board[EP_STATUS] = toX;
9959            }
9960        }
9961
9962        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
9963        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
9964        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
9965        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
9966
9967        for(i=0; i<nrCastlingRights; i++) {
9968            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9969               board[CASTLING][i] == toX   && castlingRank[i] == toY
9970              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9971        }
9972
9973        if(gameInfo.variant == VariantSChess) { // update virginity
9974            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9975            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9976            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9977            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9978        }
9979
9980      if (fromX == toX && fromY == toY) return;
9981
9982      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9983      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9984      if(gameInfo.variant == VariantKnightmate)
9985          king += (int) WhiteUnicorn - (int) WhiteKing;
9986
9987     /* Code added by Tord: */
9988     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9989     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9990         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9991       board[EP_STATUS] = EP_NONE; // capture was fake!
9992       board[fromY][fromX] = EmptySquare;
9993       board[toY][toX] = EmptySquare;
9994       if((toX > fromX) != (piece == WhiteRook)) {
9995         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9996       } else {
9997         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9998       }
9999     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10000                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10001       board[EP_STATUS] = EP_NONE;
10002       board[fromY][fromX] = EmptySquare;
10003       board[toY][toX] = EmptySquare;
10004       if((toX > fromX) != (piece == BlackRook)) {
10005         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10006       } else {
10007         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10008       }
10009     /* End of code added by Tord */
10010
10011     } else if (board[fromY][fromX] == king
10012         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10013         && toY == fromY && toX > fromX+1) {
10014         board[fromY][fromX] = EmptySquare;
10015         board[toY][toX] = king;
10016         for(rookX=BOARD_RGHT-1; board[toY][rookX] == DarkSquare && rookX > toX + 1; rookX--);
10017         board[toY][toX-1] = board[fromY][rookX];
10018         board[fromY][rookX] = EmptySquare;
10019     } else if (board[fromY][fromX] == king
10020         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10021                && toY == fromY && toX < fromX-1) {
10022         board[fromY][fromX] = EmptySquare;
10023         board[toY][toX] = king;
10024         for(rookX=BOARD_LEFT; board[toY][rookX] == DarkSquare && rookX < toX - 1; rookX++);
10025         board[toY][toX+1] = board[fromY][rookX];
10026         board[fromY][rookX] = EmptySquare;
10027     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10028                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10029                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10030                ) {
10031         /* white pawn promotion */
10032         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10033         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10034             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10035         board[fromY][fromX] = EmptySquare;
10036     } else if ((fromY >= BOARD_HEIGHT>>1)
10037                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10038                && (toX != fromX)
10039                && gameInfo.variant != VariantXiangqi
10040                && gameInfo.variant != VariantBerolina
10041                && (board[fromY][fromX] == WhitePawn)
10042                && (board[toY][toX] == EmptySquare)) {
10043         board[fromY][fromX] = EmptySquare;
10044         board[toY][toX] = WhitePawn;
10045         if(toY == board[EP_RANK] - 128 + 1)
10046             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10047         else
10048             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10049     } else if ((fromY == BOARD_HEIGHT-4)
10050                && (toX == fromX)
10051                && gameInfo.variant == VariantBerolina
10052                && (board[fromY][fromX] == WhitePawn)
10053                && (board[toY][toX] == EmptySquare)) {
10054         board[fromY][fromX] = EmptySquare;
10055         board[toY][toX] = WhitePawn;
10056         if(oldEP & EP_BEROLIN_A) {
10057                 captured = board[fromY][fromX-1];
10058                 board[fromY][fromX-1] = EmptySquare;
10059         }else{  captured = board[fromY][fromX+1];
10060                 board[fromY][fromX+1] = EmptySquare;
10061         }
10062     } else if (board[fromY][fromX] == king
10063         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10064                && toY == fromY && toX > fromX+1) {
10065         board[fromY][fromX] = EmptySquare;
10066         board[toY][toX] = king;
10067         for(rookX=BOARD_RGHT-1; board[toY][rookX] == DarkSquare && rookX > toX + 1; rookX--);
10068         board[toY][toX-1] = board[fromY][rookX];
10069         board[fromY][rookX] = EmptySquare;
10070     } else if (board[fromY][fromX] == king
10071         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10072                && toY == fromY && toX < fromX-1) {
10073         board[fromY][fromX] = EmptySquare;
10074         board[toY][toX] = king;
10075         for(rookX=BOARD_LEFT; board[toY][rookX] == DarkSquare && rookX < toX - 1; rookX++);
10076         board[toY][toX+1] = board[fromY][rookX];
10077         board[fromY][rookX] = EmptySquare;
10078     } else if (fromY == 7 && fromX == 3
10079                && board[fromY][fromX] == BlackKing
10080                && toY == 7 && toX == 5) {
10081         board[fromY][fromX] = EmptySquare;
10082         board[toY][toX] = BlackKing;
10083         board[fromY][7] = EmptySquare;
10084         board[toY][4] = BlackRook;
10085     } else if (fromY == 7 && fromX == 3
10086                && board[fromY][fromX] == BlackKing
10087                && toY == 7 && toX == 1) {
10088         board[fromY][fromX] = EmptySquare;
10089         board[toY][toX] = BlackKing;
10090         board[fromY][0] = EmptySquare;
10091         board[toY][2] = BlackRook;
10092     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10093                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10094                && toY < promoRank && promoChar
10095                ) {
10096         /* black pawn promotion */
10097         board[toY][toX] = CharToPiece(ToLower(promoChar));
10098         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10099             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10100         board[fromY][fromX] = EmptySquare;
10101     } else if ((fromY < BOARD_HEIGHT>>1)
10102                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10103                && (toX != fromX)
10104                && gameInfo.variant != VariantXiangqi
10105                && gameInfo.variant != VariantBerolina
10106                && (board[fromY][fromX] == BlackPawn)
10107                && (board[toY][toX] == EmptySquare)) {
10108         board[fromY][fromX] = EmptySquare;
10109         board[toY][toX] = BlackPawn;
10110         if(toY == board[EP_RANK] - 128 - 1)
10111             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10112         else
10113             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10114     } else if ((fromY == 3)
10115                && (toX == fromX)
10116                && gameInfo.variant == VariantBerolina
10117                && (board[fromY][fromX] == BlackPawn)
10118                && (board[toY][toX] == EmptySquare)) {
10119         board[fromY][fromX] = EmptySquare;
10120         board[toY][toX] = BlackPawn;
10121         if(oldEP & EP_BEROLIN_A) {
10122                 captured = board[fromY][fromX-1];
10123                 board[fromY][fromX-1] = EmptySquare;
10124         }else{  captured = board[fromY][fromX+1];
10125                 board[fromY][fromX+1] = EmptySquare;
10126         }
10127     } else {
10128         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10129         board[fromY][fromX] = EmptySquare;
10130         board[toY][toX] = piece;
10131     }
10132   }
10133
10134     if (gameInfo.holdingsWidth != 0) {
10135
10136       /* !!A lot more code needs to be written to support holdings  */
10137       /* [HGM] OK, so I have written it. Holdings are stored in the */
10138       /* penultimate board files, so they are automaticlly stored   */
10139       /* in the game history.                                       */
10140       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10141                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10142         /* Delete from holdings, by decreasing count */
10143         /* and erasing image if necessary            */
10144         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10145         if(p < (int) BlackPawn) { /* white drop */
10146              p -= (int)WhitePawn;
10147                  p = PieceToNumber((ChessSquare)p);
10148              if(p >= gameInfo.holdingsSize) p = 0;
10149              if(--board[p][BOARD_WIDTH-2] <= 0)
10150                   board[p][BOARD_WIDTH-1] = EmptySquare;
10151              if((int)board[p][BOARD_WIDTH-2] < 0)
10152                         board[p][BOARD_WIDTH-2] = 0;
10153         } else {                  /* black drop */
10154              p -= (int)BlackPawn;
10155                  p = PieceToNumber((ChessSquare)p);
10156              if(p >= gameInfo.holdingsSize) p = 0;
10157              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10158                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10159              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10160                         board[BOARD_HEIGHT-1-p][1] = 0;
10161         }
10162       }
10163       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10164           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10165         /* [HGM] holdings: Add to holdings, if holdings exist */
10166         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10167                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10168                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10169         }
10170         p = (int) captured;
10171         if (p >= (int) BlackPawn) {
10172           p -= (int)BlackPawn;
10173           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10174                   /* Restore shogi-promoted piece to its original  first */
10175                   captured = (ChessSquare) (DEMOTED captured);
10176                   p = DEMOTED p;
10177           }
10178           p = PieceToNumber((ChessSquare)p);
10179           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10180           board[p][BOARD_WIDTH-2]++;
10181           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10182         } else {
10183           p -= (int)WhitePawn;
10184           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10185                   captured = (ChessSquare) (DEMOTED captured);
10186                   p = DEMOTED p;
10187           }
10188           p = PieceToNumber((ChessSquare)p);
10189           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10190           board[BOARD_HEIGHT-1-p][1]++;
10191           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10192         }
10193       }
10194     } else if (gameInfo.variant == VariantAtomic) {
10195       if (captured != EmptySquare) {
10196         int y, x;
10197         for (y = toY-1; y <= toY+1; y++) {
10198           for (x = toX-1; x <= toX+1; x++) {
10199             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10200                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10201               board[y][x] = EmptySquare;
10202             }
10203           }
10204         }
10205         board[toY][toX] = EmptySquare;
10206       }
10207     }
10208
10209     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10210         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10211     } else
10212     if(promoChar == '+') {
10213         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10214         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10215         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10216           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10217     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10218         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10219         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10220            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10221         board[toY][toX] = newPiece;
10222     }
10223     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10224                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10225         // [HGM] superchess: take promotion piece out of holdings
10226         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10227         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10228             if(!--board[k][BOARD_WIDTH-2])
10229                 board[k][BOARD_WIDTH-1] = EmptySquare;
10230         } else {
10231             if(!--board[BOARD_HEIGHT-1-k][1])
10232                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10233         }
10234     }
10235 }
10236
10237 /* Updates forwardMostMove */
10238 void
10239 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10240 {
10241     int x = toX, y = toY;
10242     char *s = parseList[forwardMostMove];
10243     ChessSquare p = boards[forwardMostMove][toY][toX];
10244 //    forwardMostMove++; // [HGM] bare: moved downstream
10245
10246     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10247     (void) CoordsToAlgebraic(boards[forwardMostMove],
10248                              PosFlags(forwardMostMove),
10249                              fromY, fromX, y, x, promoChar,
10250                              s);
10251     if(killX >= 0 && killY >= 0)
10252         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10253
10254     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10255         int timeLeft; static int lastLoadFlag=0; int king, piece;
10256         piece = boards[forwardMostMove][fromY][fromX];
10257         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10258         if(gameInfo.variant == VariantKnightmate)
10259             king += (int) WhiteUnicorn - (int) WhiteKing;
10260         if(forwardMostMove == 0) {
10261             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10262                 fprintf(serverMoves, "%s;", UserName());
10263             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10264                 fprintf(serverMoves, "%s;", second.tidy);
10265             fprintf(serverMoves, "%s;", first.tidy);
10266             if(gameMode == MachinePlaysWhite)
10267                 fprintf(serverMoves, "%s;", UserName());
10268             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10269                 fprintf(serverMoves, "%s;", second.tidy);
10270         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10271         lastLoadFlag = loadFlag;
10272         // print base move
10273         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10274         // print castling suffix
10275         if( toY == fromY && piece == king ) {
10276             if(toX-fromX > 1)
10277                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10278             if(fromX-toX >1)
10279                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10280         }
10281         // e.p. suffix
10282         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10283              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10284              boards[forwardMostMove][toY][toX] == EmptySquare
10285              && fromX != toX && fromY != toY)
10286                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10287         // promotion suffix
10288         if(promoChar != NULLCHAR) {
10289             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10290                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10291                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10292             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10293         }
10294         if(!loadFlag) {
10295                 char buf[MOVE_LEN*2], *p; int len;
10296             fprintf(serverMoves, "/%d/%d",
10297                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10298             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10299             else                      timeLeft = blackTimeRemaining/1000;
10300             fprintf(serverMoves, "/%d", timeLeft);
10301                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10302                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10303                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10304                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10305             fprintf(serverMoves, "/%s", buf);
10306         }
10307         fflush(serverMoves);
10308     }
10309
10310     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10311         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10312       return;
10313     }
10314     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10315     if (commentList[forwardMostMove+1] != NULL) {
10316         free(commentList[forwardMostMove+1]);
10317         commentList[forwardMostMove+1] = NULL;
10318     }
10319     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10320     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10321     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10322     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10323     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10324     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10325     adjustedClock = FALSE;
10326     gameInfo.result = GameUnfinished;
10327     if (gameInfo.resultDetails != NULL) {
10328         free(gameInfo.resultDetails);
10329         gameInfo.resultDetails = NULL;
10330     }
10331     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10332                               moveList[forwardMostMove - 1]);
10333     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10334       case MT_NONE:
10335       case MT_STALEMATE:
10336       default:
10337         break;
10338       case MT_CHECK:
10339         if(!IS_SHOGI(gameInfo.variant))
10340             strcat(parseList[forwardMostMove - 1], "+");
10341         break;
10342       case MT_CHECKMATE:
10343       case MT_STAINMATE:
10344         strcat(parseList[forwardMostMove - 1], "#");
10345         break;
10346     }
10347 }
10348
10349 /* Updates currentMove if not pausing */
10350 void
10351 ShowMove (int fromX, int fromY, int toX, int toY)
10352 {
10353     int instant = (gameMode == PlayFromGameFile) ?
10354         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10355     if(appData.noGUI) return;
10356     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10357         if (!instant) {
10358             if (forwardMostMove == currentMove + 1) {
10359                 AnimateMove(boards[forwardMostMove - 1],
10360                             fromX, fromY, toX, toY);
10361             }
10362         }
10363         currentMove = forwardMostMove;
10364     }
10365
10366     killX = killY = -1; // [HGM] lion: used up
10367
10368     if (instant) return;
10369
10370     DisplayMove(currentMove - 1);
10371     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10372             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10373                 SetHighlights(fromX, fromY, toX, toY);
10374             }
10375     }
10376     DrawPosition(FALSE, boards[currentMove]);
10377     DisplayBothClocks();
10378     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10379 }
10380
10381 void
10382 SendEgtPath (ChessProgramState *cps)
10383 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10384         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10385
10386         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10387
10388         while(*p) {
10389             char c, *q = name+1, *r, *s;
10390
10391             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10392             while(*p && *p != ',') *q++ = *p++;
10393             *q++ = ':'; *q = 0;
10394             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10395                 strcmp(name, ",nalimov:") == 0 ) {
10396                 // take nalimov path from the menu-changeable option first, if it is defined
10397               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10398                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10399             } else
10400             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10401                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10402                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10403                 s = r = StrStr(s, ":") + 1; // beginning of path info
10404                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10405                 c = *r; *r = 0;             // temporarily null-terminate path info
10406                     *--q = 0;               // strip of trailig ':' from name
10407                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10408                 *r = c;
10409                 SendToProgram(buf,cps);     // send egtbpath command for this format
10410             }
10411             if(*p == ',') p++; // read away comma to position for next format name
10412         }
10413 }
10414
10415 static int
10416 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10417 {
10418       int width = 8, height = 8, holdings = 0;             // most common sizes
10419       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10420       // correct the deviations default for each variant
10421       if( v == VariantXiangqi ) width = 9,  height = 10;
10422       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10423       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10424       if( v == VariantCapablanca || v == VariantCapaRandom ||
10425           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10426                                 width = 10;
10427       if( v == VariantCourier ) width = 12;
10428       if( v == VariantSuper )                            holdings = 8;
10429       if( v == VariantGreat )   width = 10,              holdings = 8;
10430       if( v == VariantSChess )                           holdings = 7;
10431       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10432       if( v == VariantChuChess) width = 10, height = 10;
10433       if( v == VariantChu )     width = 12, height = 12;
10434       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10435              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10436              holdingsSize >= 0 && holdingsSize != holdings;
10437 }
10438
10439 char variantError[MSG_SIZ];
10440
10441 char *
10442 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10443 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10444       char *p, *variant = VariantName(v);
10445       static char b[MSG_SIZ];
10446       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10447            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10448                                                holdingsSize, variant); // cook up sized variant name
10449            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10450            if(StrStr(list, b) == NULL) {
10451                // specific sized variant not known, check if general sizing allowed
10452                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10453                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10454                             boardWidth, boardHeight, holdingsSize, engine);
10455                    return NULL;
10456                }
10457                /* [HGM] here we really should compare with the maximum supported board size */
10458            }
10459       } else snprintf(b, MSG_SIZ,"%s", variant);
10460       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10461       p = StrStr(list, b);
10462       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10463       if(p == NULL) {
10464           // occurs not at all in list, or only as sub-string
10465           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10466           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10467               int l = strlen(variantError);
10468               char *q;
10469               while(p != list && p[-1] != ',') p--;
10470               q = strchr(p, ',');
10471               if(q) *q = NULLCHAR;
10472               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10473               if(q) *q= ',';
10474           }
10475           return NULL;
10476       }
10477       return b;
10478 }
10479
10480 void
10481 InitChessProgram (ChessProgramState *cps, int setup)
10482 /* setup needed to setup FRC opening position */
10483 {
10484     char buf[MSG_SIZ], *b;
10485     if (appData.noChessProgram) return;
10486     hintRequested = FALSE;
10487     bookRequested = FALSE;
10488
10489     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10490     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10491     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10492     if(cps->memSize) { /* [HGM] memory */
10493       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10494         SendToProgram(buf, cps);
10495     }
10496     SendEgtPath(cps); /* [HGM] EGT */
10497     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10498       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10499         SendToProgram(buf, cps);
10500     }
10501
10502     setboardSpoiledMachineBlack = FALSE;
10503     SendToProgram(cps->initString, cps);
10504     if (gameInfo.variant != VariantNormal &&
10505         gameInfo.variant != VariantLoadable
10506         /* [HGM] also send variant if board size non-standard */
10507         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10508
10509       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10510                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10511       if (b == NULL) {
10512         VariantClass v;
10513         char c, *q = cps->variants, *p = strchr(q, ',');
10514         if(p) *p = NULLCHAR;
10515         v = StringToVariant(q);
10516         DisplayError(variantError, 0);
10517         if(v != VariantUnknown && cps == &first) {
10518             int w, h, s;
10519             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10520                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10521             ASSIGN(appData.variant, q);
10522             Reset(TRUE, FALSE);
10523         }
10524         if(p) *p = ',';
10525         return;
10526       }
10527
10528       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10529       SendToProgram(buf, cps);
10530     }
10531     currentlyInitializedVariant = gameInfo.variant;
10532
10533     /* [HGM] send opening position in FRC to first engine */
10534     if(setup) {
10535           SendToProgram("force\n", cps);
10536           SendBoard(cps, 0);
10537           /* engine is now in force mode! Set flag to wake it up after first move. */
10538           setboardSpoiledMachineBlack = 1;
10539     }
10540
10541     if (cps->sendICS) {
10542       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10543       SendToProgram(buf, cps);
10544     }
10545     cps->maybeThinking = FALSE;
10546     cps->offeredDraw = 0;
10547     if (!appData.icsActive) {
10548         SendTimeControl(cps, movesPerSession, timeControl,
10549                         timeIncrement, appData.searchDepth,
10550                         searchTime);
10551     }
10552     if (appData.showThinking
10553         // [HGM] thinking: four options require thinking output to be sent
10554         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10555                                 ) {
10556         SendToProgram("post\n", cps);
10557     }
10558     SendToProgram("hard\n", cps);
10559     if (!appData.ponderNextMove) {
10560         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10561            it without being sure what state we are in first.  "hard"
10562            is not a toggle, so that one is OK.
10563          */
10564         SendToProgram("easy\n", cps);
10565     }
10566     if (cps->usePing) {
10567       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10568       SendToProgram(buf, cps);
10569     }
10570     cps->initDone = TRUE;
10571     ClearEngineOutputPane(cps == &second);
10572 }
10573
10574
10575 void
10576 ResendOptions (ChessProgramState *cps)
10577 { // send the stored value of the options
10578   int i;
10579   char buf[MSG_SIZ];
10580   Option *opt = cps->option;
10581   for(i=0; i<cps->nrOptions; i++, opt++) {
10582       switch(opt->type) {
10583         case Spin:
10584         case Slider:
10585         case CheckBox:
10586             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10587           break;
10588         case ComboBox:
10589           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10590           break;
10591         default:
10592             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10593           break;
10594         case Button:
10595         case SaveButton:
10596           continue;
10597       }
10598       SendToProgram(buf, cps);
10599   }
10600 }
10601
10602 void
10603 StartChessProgram (ChessProgramState *cps)
10604 {
10605     char buf[MSG_SIZ];
10606     int err;
10607
10608     if (appData.noChessProgram) return;
10609     cps->initDone = FALSE;
10610
10611     if (strcmp(cps->host, "localhost") == 0) {
10612         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10613     } else if (*appData.remoteShell == NULLCHAR) {
10614         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10615     } else {
10616         if (*appData.remoteUser == NULLCHAR) {
10617           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10618                     cps->program);
10619         } else {
10620           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10621                     cps->host, appData.remoteUser, cps->program);
10622         }
10623         err = StartChildProcess(buf, "", &cps->pr);
10624     }
10625
10626     if (err != 0) {
10627       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10628         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10629         if(cps != &first) return;
10630         appData.noChessProgram = TRUE;
10631         ThawUI();
10632         SetNCPMode();
10633 //      DisplayFatalError(buf, err, 1);
10634 //      cps->pr = NoProc;
10635 //      cps->isr = NULL;
10636         return;
10637     }
10638
10639     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10640     if (cps->protocolVersion > 1) {
10641       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10642       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10643         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10644         cps->comboCnt = 0;  //                and values of combo boxes
10645       }
10646       SendToProgram(buf, cps);
10647       if(cps->reload) ResendOptions(cps);
10648     } else {
10649       SendToProgram("xboard\n", cps);
10650     }
10651 }
10652
10653 void
10654 TwoMachinesEventIfReady P((void))
10655 {
10656   static int curMess = 0;
10657   if (first.lastPing != first.lastPong) {
10658     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10659     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10660     return;
10661   }
10662   if (second.lastPing != second.lastPong) {
10663     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10664     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10665     return;
10666   }
10667   DisplayMessage("", ""); curMess = 0;
10668   TwoMachinesEvent();
10669 }
10670
10671 char *
10672 MakeName (char *template)
10673 {
10674     time_t clock;
10675     struct tm *tm;
10676     static char buf[MSG_SIZ];
10677     char *p = buf;
10678     int i;
10679
10680     clock = time((time_t *)NULL);
10681     tm = localtime(&clock);
10682
10683     while(*p++ = *template++) if(p[-1] == '%') {
10684         switch(*template++) {
10685           case 0:   *p = 0; return buf;
10686           case 'Y': i = tm->tm_year+1900; break;
10687           case 'y': i = tm->tm_year-100; break;
10688           case 'M': i = tm->tm_mon+1; break;
10689           case 'd': i = tm->tm_mday; break;
10690           case 'h': i = tm->tm_hour; break;
10691           case 'm': i = tm->tm_min; break;
10692           case 's': i = tm->tm_sec; break;
10693           default:  i = 0;
10694         }
10695         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10696     }
10697     return buf;
10698 }
10699
10700 int
10701 CountPlayers (char *p)
10702 {
10703     int n = 0;
10704     while(p = strchr(p, '\n')) p++, n++; // count participants
10705     return n;
10706 }
10707
10708 FILE *
10709 WriteTourneyFile (char *results, FILE *f)
10710 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10711     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10712     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10713         // create a file with tournament description
10714         fprintf(f, "-participants {%s}\n", appData.participants);
10715         fprintf(f, "-seedBase %d\n", appData.seedBase);
10716         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10717         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10718         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10719         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10720         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10721         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10722         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10723         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10724         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10725         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10726         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10727         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10728         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10729         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10730         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10731         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10732         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10733         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10734         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10735         fprintf(f, "-smpCores %d\n", appData.smpCores);
10736         if(searchTime > 0)
10737                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10738         else {
10739                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10740                 fprintf(f, "-tc %s\n", appData.timeControl);
10741                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10742         }
10743         fprintf(f, "-results \"%s\"\n", results);
10744     }
10745     return f;
10746 }
10747
10748 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10749
10750 void
10751 Substitute (char *participants, int expunge)
10752 {
10753     int i, changed, changes=0, nPlayers=0;
10754     char *p, *q, *r, buf[MSG_SIZ];
10755     if(participants == NULL) return;
10756     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10757     r = p = participants; q = appData.participants;
10758     while(*p && *p == *q) {
10759         if(*p == '\n') r = p+1, nPlayers++;
10760         p++; q++;
10761     }
10762     if(*p) { // difference
10763         while(*p && *p++ != '\n');
10764         while(*q && *q++ != '\n');
10765       changed = nPlayers;
10766         changes = 1 + (strcmp(p, q) != 0);
10767     }
10768     if(changes == 1) { // a single engine mnemonic was changed
10769         q = r; while(*q) nPlayers += (*q++ == '\n');
10770         p = buf; while(*r && (*p = *r++) != '\n') p++;
10771         *p = NULLCHAR;
10772         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10773         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10774         if(mnemonic[i]) { // The substitute is valid
10775             FILE *f;
10776             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10777                 flock(fileno(f), LOCK_EX);
10778                 ParseArgsFromFile(f);
10779                 fseek(f, 0, SEEK_SET);
10780                 FREE(appData.participants); appData.participants = participants;
10781                 if(expunge) { // erase results of replaced engine
10782                     int len = strlen(appData.results), w, b, dummy;
10783                     for(i=0; i<len; i++) {
10784                         Pairing(i, nPlayers, &w, &b, &dummy);
10785                         if((w == changed || b == changed) && appData.results[i] == '*') {
10786                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10787                             fclose(f);
10788                             return;
10789                         }
10790                     }
10791                     for(i=0; i<len; i++) {
10792                         Pairing(i, nPlayers, &w, &b, &dummy);
10793                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10794                     }
10795                 }
10796                 WriteTourneyFile(appData.results, f);
10797                 fclose(f); // release lock
10798                 return;
10799             }
10800         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10801     }
10802     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10803     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10804     free(participants);
10805     return;
10806 }
10807
10808 int
10809 CheckPlayers (char *participants)
10810 {
10811         int i;
10812         char buf[MSG_SIZ], *p;
10813         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10814         while(p = strchr(participants, '\n')) {
10815             *p = NULLCHAR;
10816             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10817             if(!mnemonic[i]) {
10818                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10819                 *p = '\n';
10820                 DisplayError(buf, 0);
10821                 return 1;
10822             }
10823             *p = '\n';
10824             participants = p + 1;
10825         }
10826         return 0;
10827 }
10828
10829 int
10830 CreateTourney (char *name)
10831 {
10832         FILE *f;
10833         if(matchMode && strcmp(name, appData.tourneyFile)) {
10834              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10835         }
10836         if(name[0] == NULLCHAR) {
10837             if(appData.participants[0])
10838                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10839             return 0;
10840         }
10841         f = fopen(name, "r");
10842         if(f) { // file exists
10843             ASSIGN(appData.tourneyFile, name);
10844             ParseArgsFromFile(f); // parse it
10845         } else {
10846             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10847             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10848                 DisplayError(_("Not enough participants"), 0);
10849                 return 0;
10850             }
10851             if(CheckPlayers(appData.participants)) return 0;
10852             ASSIGN(appData.tourneyFile, name);
10853             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10854             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10855         }
10856         fclose(f);
10857         appData.noChessProgram = FALSE;
10858         appData.clockMode = TRUE;
10859         SetGNUMode();
10860         return 1;
10861 }
10862
10863 int
10864 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10865 {
10866     char buf[MSG_SIZ], *p, *q;
10867     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10868     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10869     skip = !all && group[0]; // if group requested, we start in skip mode
10870     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10871         p = names; q = buf; header = 0;
10872         while(*p && *p != '\n') *q++ = *p++;
10873         *q = 0;
10874         if(*p == '\n') p++;
10875         if(buf[0] == '#') {
10876             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10877             depth++; // we must be entering a new group
10878             if(all) continue; // suppress printing group headers when complete list requested
10879             header = 1;
10880             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10881         }
10882         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10883         if(engineList[i]) free(engineList[i]);
10884         engineList[i] = strdup(buf);
10885         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10886         if(engineMnemonic[i]) free(engineMnemonic[i]);
10887         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10888             strcat(buf, " (");
10889             sscanf(q + 8, "%s", buf + strlen(buf));
10890             strcat(buf, ")");
10891         }
10892         engineMnemonic[i] = strdup(buf);
10893         i++;
10894     }
10895     engineList[i] = engineMnemonic[i] = NULL;
10896     return i;
10897 }
10898
10899 // following implemented as macro to avoid type limitations
10900 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10901
10902 void
10903 SwapEngines (int n)
10904 {   // swap settings for first engine and other engine (so far only some selected options)
10905     int h;
10906     char *p;
10907     if(n == 0) return;
10908     SWAP(directory, p)
10909     SWAP(chessProgram, p)
10910     SWAP(isUCI, h)
10911     SWAP(hasOwnBookUCI, h)
10912     SWAP(protocolVersion, h)
10913     SWAP(reuse, h)
10914     SWAP(scoreIsAbsolute, h)
10915     SWAP(timeOdds, h)
10916     SWAP(logo, p)
10917     SWAP(pgnName, p)
10918     SWAP(pvSAN, h)
10919     SWAP(engOptions, p)
10920     SWAP(engInitString, p)
10921     SWAP(computerString, p)
10922     SWAP(features, p)
10923     SWAP(fenOverride, p)
10924     SWAP(NPS, h)
10925     SWAP(accumulateTC, h)
10926     SWAP(drawDepth, h)
10927     SWAP(host, p)
10928     SWAP(pseudo, h)
10929 }
10930
10931 int
10932 GetEngineLine (char *s, int n)
10933 {
10934     int i;
10935     char buf[MSG_SIZ];
10936     extern char *icsNames;
10937     if(!s || !*s) return 0;
10938     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10939     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10940     if(!mnemonic[i]) return 0;
10941     if(n == 11) return 1; // just testing if there was a match
10942     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10943     if(n == 1) SwapEngines(n);
10944     ParseArgsFromString(buf);
10945     if(n == 1) SwapEngines(n);
10946     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10947         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10948         ParseArgsFromString(buf);
10949     }
10950     return 1;
10951 }
10952
10953 int
10954 SetPlayer (int player, char *p)
10955 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10956     int i;
10957     char buf[MSG_SIZ], *engineName;
10958     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10959     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10960     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10961     if(mnemonic[i]) {
10962         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10963         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10964         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10965         ParseArgsFromString(buf);
10966     } else { // no engine with this nickname is installed!
10967         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10968         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10969         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10970         ModeHighlight();
10971         DisplayError(buf, 0);
10972         return 0;
10973     }
10974     free(engineName);
10975     return i;
10976 }
10977
10978 char *recentEngines;
10979
10980 void
10981 RecentEngineEvent (int nr)
10982 {
10983     int n;
10984 //    SwapEngines(1); // bump first to second
10985 //    ReplaceEngine(&second, 1); // and load it there
10986     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10987     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10988     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10989         ReplaceEngine(&first, 0);
10990         FloatToFront(&appData.recentEngineList, command[n]);
10991     }
10992 }
10993
10994 int
10995 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10996 {   // determine players from game number
10997     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10998
10999     if(appData.tourneyType == 0) {
11000         roundsPerCycle = (nPlayers - 1) | 1;
11001         pairingsPerRound = nPlayers / 2;
11002     } else if(appData.tourneyType > 0) {
11003         roundsPerCycle = nPlayers - appData.tourneyType;
11004         pairingsPerRound = appData.tourneyType;
11005     }
11006     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11007     gamesPerCycle = gamesPerRound * roundsPerCycle;
11008     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11009     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11010     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11011     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11012     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11013     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11014
11015     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11016     if(appData.roundSync) *syncInterval = gamesPerRound;
11017
11018     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11019
11020     if(appData.tourneyType == 0) {
11021         if(curPairing == (nPlayers-1)/2 ) {
11022             *whitePlayer = curRound;
11023             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11024         } else {
11025             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11026             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11027             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11028             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11029         }
11030     } else if(appData.tourneyType > 1) {
11031         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11032         *whitePlayer = curRound + appData.tourneyType;
11033     } else if(appData.tourneyType > 0) {
11034         *whitePlayer = curPairing;
11035         *blackPlayer = curRound + appData.tourneyType;
11036     }
11037
11038     // take care of white/black alternation per round.
11039     // For cycles and games this is already taken care of by default, derived from matchGame!
11040     return curRound & 1;
11041 }
11042
11043 int
11044 NextTourneyGame (int nr, int *swapColors)
11045 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11046     char *p, *q;
11047     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11048     FILE *tf;
11049     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11050     tf = fopen(appData.tourneyFile, "r");
11051     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11052     ParseArgsFromFile(tf); fclose(tf);
11053     InitTimeControls(); // TC might be altered from tourney file
11054
11055     nPlayers = CountPlayers(appData.participants); // count participants
11056     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11057     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11058
11059     if(syncInterval) {
11060         p = q = appData.results;
11061         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11062         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11063             DisplayMessage(_("Waiting for other game(s)"),"");
11064             waitingForGame = TRUE;
11065             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11066             return 0;
11067         }
11068         waitingForGame = FALSE;
11069     }
11070
11071     if(appData.tourneyType < 0) {
11072         if(nr>=0 && !pairingReceived) {
11073             char buf[1<<16];
11074             if(pairing.pr == NoProc) {
11075                 if(!appData.pairingEngine[0]) {
11076                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11077                     return 0;
11078                 }
11079                 StartChessProgram(&pairing); // starts the pairing engine
11080             }
11081             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11082             SendToProgram(buf, &pairing);
11083             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11084             SendToProgram(buf, &pairing);
11085             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11086         }
11087         pairingReceived = 0;                              // ... so we continue here
11088         *swapColors = 0;
11089         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11090         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11091         matchGame = 1; roundNr = nr / syncInterval + 1;
11092     }
11093
11094     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11095
11096     // redefine engines, engine dir, etc.
11097     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11098     if(first.pr == NoProc) {
11099       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11100       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11101     }
11102     if(second.pr == NoProc) {
11103       SwapEngines(1);
11104       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11105       SwapEngines(1);         // and make that valid for second engine by swapping
11106       InitEngine(&second, 1);
11107     }
11108     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11109     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11110     return OK;
11111 }
11112
11113 void
11114 NextMatchGame ()
11115 {   // performs game initialization that does not invoke engines, and then tries to start the game
11116     int res, firstWhite, swapColors = 0;
11117     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11118     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
11119         char buf[MSG_SIZ];
11120         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11121         if(strcmp(buf, currentDebugFile)) { // name has changed
11122             FILE *f = fopen(buf, "w");
11123             if(f) { // if opening the new file failed, just keep using the old one
11124                 ASSIGN(currentDebugFile, buf);
11125                 fclose(debugFP);
11126                 debugFP = f;
11127             }
11128             if(appData.serverFileName) {
11129                 if(serverFP) fclose(serverFP);
11130                 serverFP = fopen(appData.serverFileName, "w");
11131                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11132                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11133             }
11134         }
11135     }
11136     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11137     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11138     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11139     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11140     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11141     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11142     Reset(FALSE, first.pr != NoProc);
11143     res = LoadGameOrPosition(matchGame); // setup game
11144     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11145     if(!res) return; // abort when bad game/pos file
11146     TwoMachinesEvent();
11147 }
11148
11149 void
11150 UserAdjudicationEvent (int result)
11151 {
11152     ChessMove gameResult = GameIsDrawn;
11153
11154     if( result > 0 ) {
11155         gameResult = WhiteWins;
11156     }
11157     else if( result < 0 ) {
11158         gameResult = BlackWins;
11159     }
11160
11161     if( gameMode == TwoMachinesPlay ) {
11162         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11163     }
11164 }
11165
11166
11167 // [HGM] save: calculate checksum of game to make games easily identifiable
11168 int
11169 StringCheckSum (char *s)
11170 {
11171         int i = 0;
11172         if(s==NULL) return 0;
11173         while(*s) i = i*259 + *s++;
11174         return i;
11175 }
11176
11177 int
11178 GameCheckSum ()
11179 {
11180         int i, sum=0;
11181         for(i=backwardMostMove; i<forwardMostMove; i++) {
11182                 sum += pvInfoList[i].depth;
11183                 sum += StringCheckSum(parseList[i]);
11184                 sum += StringCheckSum(commentList[i]);
11185                 sum *= 261;
11186         }
11187         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11188         return sum + StringCheckSum(commentList[i]);
11189 } // end of save patch
11190
11191 void
11192 GameEnds (ChessMove result, char *resultDetails, int whosays)
11193 {
11194     GameMode nextGameMode;
11195     int isIcsGame;
11196     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11197
11198     if(endingGame) return; /* [HGM] crash: forbid recursion */
11199     endingGame = 1;
11200     if(twoBoards) { // [HGM] dual: switch back to one board
11201         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11202         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11203     }
11204     if (appData.debugMode) {
11205       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11206               result, resultDetails ? resultDetails : "(null)", whosays);
11207     }
11208
11209     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11210
11211     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11212
11213     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11214         /* If we are playing on ICS, the server decides when the
11215            game is over, but the engine can offer to draw, claim
11216            a draw, or resign.
11217          */
11218 #if ZIPPY
11219         if (appData.zippyPlay && first.initDone) {
11220             if (result == GameIsDrawn) {
11221                 /* In case draw still needs to be claimed */
11222                 SendToICS(ics_prefix);
11223                 SendToICS("draw\n");
11224             } else if (StrCaseStr(resultDetails, "resign")) {
11225                 SendToICS(ics_prefix);
11226                 SendToICS("resign\n");
11227             }
11228         }
11229 #endif
11230         endingGame = 0; /* [HGM] crash */
11231         return;
11232     }
11233
11234     /* If we're loading the game from a file, stop */
11235     if (whosays == GE_FILE) {
11236       (void) StopLoadGameTimer();
11237       gameFileFP = NULL;
11238     }
11239
11240     /* Cancel draw offers */
11241     first.offeredDraw = second.offeredDraw = 0;
11242
11243     /* If this is an ICS game, only ICS can really say it's done;
11244        if not, anyone can. */
11245     isIcsGame = (gameMode == IcsPlayingWhite ||
11246                  gameMode == IcsPlayingBlack ||
11247                  gameMode == IcsObserving    ||
11248                  gameMode == IcsExamining);
11249
11250     if (!isIcsGame || whosays == GE_ICS) {
11251         /* OK -- not an ICS game, or ICS said it was done */
11252         StopClocks();
11253         if (!isIcsGame && !appData.noChessProgram)
11254           SetUserThinkingEnables();
11255
11256         /* [HGM] if a machine claims the game end we verify this claim */
11257         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11258             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11259                 char claimer;
11260                 ChessMove trueResult = (ChessMove) -1;
11261
11262                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11263                                             first.twoMachinesColor[0] :
11264                                             second.twoMachinesColor[0] ;
11265
11266                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11267                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11268                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11269                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11270                 } else
11271                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11272                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11273                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11274                 } else
11275                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11276                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11277                 }
11278
11279                 // now verify win claims, but not in drop games, as we don't understand those yet
11280                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11281                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11282                     (result == WhiteWins && claimer == 'w' ||
11283                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11284                       if (appData.debugMode) {
11285                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11286                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11287                       }
11288                       if(result != trueResult) {
11289                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11290                               result = claimer == 'w' ? BlackWins : WhiteWins;
11291                               resultDetails = buf;
11292                       }
11293                 } else
11294                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11295                     && (forwardMostMove <= backwardMostMove ||
11296                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11297                         (claimer=='b')==(forwardMostMove&1))
11298                                                                                   ) {
11299                       /* [HGM] verify: draws that were not flagged are false claims */
11300                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11301                       result = claimer == 'w' ? BlackWins : WhiteWins;
11302                       resultDetails = buf;
11303                 }
11304                 /* (Claiming a loss is accepted no questions asked!) */
11305             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11306                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11307                 result = GameUnfinished;
11308                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11309             }
11310             /* [HGM] bare: don't allow bare King to win */
11311             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11312                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11313                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11314                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11315                && result != GameIsDrawn)
11316             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11317                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11318                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11319                         if(p >= 0 && p <= (int)WhiteKing) k++;
11320                 }
11321                 if (appData.debugMode) {
11322                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11323                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11324                 }
11325                 if(k <= 1) {
11326                         result = GameIsDrawn;
11327                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11328                         resultDetails = buf;
11329                 }
11330             }
11331         }
11332
11333
11334         if(serverMoves != NULL && !loadFlag) { char c = '=';
11335             if(result==WhiteWins) c = '+';
11336             if(result==BlackWins) c = '-';
11337             if(resultDetails != NULL)
11338                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11339         }
11340         if (resultDetails != NULL) {
11341             gameInfo.result = result;
11342             gameInfo.resultDetails = StrSave(resultDetails);
11343
11344             /* display last move only if game was not loaded from file */
11345             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11346                 DisplayMove(currentMove - 1);
11347
11348             if (forwardMostMove != 0) {
11349                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11350                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11351                                                                 ) {
11352                     if (*appData.saveGameFile != NULLCHAR) {
11353                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11354                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11355                         else
11356                         SaveGameToFile(appData.saveGameFile, TRUE);
11357                     } else if (appData.autoSaveGames) {
11358                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11359                     }
11360                     if (*appData.savePositionFile != NULLCHAR) {
11361                         SavePositionToFile(appData.savePositionFile);
11362                     }
11363                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11364                 }
11365             }
11366
11367             /* Tell program how game ended in case it is learning */
11368             /* [HGM] Moved this to after saving the PGN, just in case */
11369             /* engine died and we got here through time loss. In that */
11370             /* case we will get a fatal error writing the pipe, which */
11371             /* would otherwise lose us the PGN.                       */
11372             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11373             /* output during GameEnds should never be fatal anymore   */
11374             if (gameMode == MachinePlaysWhite ||
11375                 gameMode == MachinePlaysBlack ||
11376                 gameMode == TwoMachinesPlay ||
11377                 gameMode == IcsPlayingWhite ||
11378                 gameMode == IcsPlayingBlack ||
11379                 gameMode == BeginningOfGame) {
11380                 char buf[MSG_SIZ];
11381                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11382                         resultDetails);
11383                 if (first.pr != NoProc) {
11384                     SendToProgram(buf, &first);
11385                 }
11386                 if (second.pr != NoProc &&
11387                     gameMode == TwoMachinesPlay) {
11388                     SendToProgram(buf, &second);
11389                 }
11390             }
11391         }
11392
11393         if (appData.icsActive) {
11394             if (appData.quietPlay &&
11395                 (gameMode == IcsPlayingWhite ||
11396                  gameMode == IcsPlayingBlack)) {
11397                 SendToICS(ics_prefix);
11398                 SendToICS("set shout 1\n");
11399             }
11400             nextGameMode = IcsIdle;
11401             ics_user_moved = FALSE;
11402             /* clean up premove.  It's ugly when the game has ended and the
11403              * premove highlights are still on the board.
11404              */
11405             if (gotPremove) {
11406               gotPremove = FALSE;
11407               ClearPremoveHighlights();
11408               DrawPosition(FALSE, boards[currentMove]);
11409             }
11410             if (whosays == GE_ICS) {
11411                 switch (result) {
11412                 case WhiteWins:
11413                     if (gameMode == IcsPlayingWhite)
11414                         PlayIcsWinSound();
11415                     else if(gameMode == IcsPlayingBlack)
11416                         PlayIcsLossSound();
11417                     break;
11418                 case BlackWins:
11419                     if (gameMode == IcsPlayingBlack)
11420                         PlayIcsWinSound();
11421                     else if(gameMode == IcsPlayingWhite)
11422                         PlayIcsLossSound();
11423                     break;
11424                 case GameIsDrawn:
11425                     PlayIcsDrawSound();
11426                     break;
11427                 default:
11428                     PlayIcsUnfinishedSound();
11429                 }
11430             }
11431             if(appData.quitNext) { ExitEvent(0); return; }
11432         } else if (gameMode == EditGame ||
11433                    gameMode == PlayFromGameFile ||
11434                    gameMode == AnalyzeMode ||
11435                    gameMode == AnalyzeFile) {
11436             nextGameMode = gameMode;
11437         } else {
11438             nextGameMode = EndOfGame;
11439         }
11440         pausing = FALSE;
11441         ModeHighlight();
11442     } else {
11443         nextGameMode = gameMode;
11444     }
11445
11446     if (appData.noChessProgram) {
11447         gameMode = nextGameMode;
11448         ModeHighlight();
11449         endingGame = 0; /* [HGM] crash */
11450         return;
11451     }
11452
11453     if (first.reuse) {
11454         /* Put first chess program into idle state */
11455         if (first.pr != NoProc &&
11456             (gameMode == MachinePlaysWhite ||
11457              gameMode == MachinePlaysBlack ||
11458              gameMode == TwoMachinesPlay ||
11459              gameMode == IcsPlayingWhite ||
11460              gameMode == IcsPlayingBlack ||
11461              gameMode == BeginningOfGame)) {
11462             SendToProgram("force\n", &first);
11463             if (first.usePing) {
11464               char buf[MSG_SIZ];
11465               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11466               SendToProgram(buf, &first);
11467             }
11468         }
11469     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11470         /* Kill off first chess program */
11471         if (first.isr != NULL)
11472           RemoveInputSource(first.isr);
11473         first.isr = NULL;
11474
11475         if (first.pr != NoProc) {
11476             ExitAnalyzeMode();
11477             DoSleep( appData.delayBeforeQuit );
11478             SendToProgram("quit\n", &first);
11479             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11480             first.reload = TRUE;
11481         }
11482         first.pr = NoProc;
11483     }
11484     if (second.reuse) {
11485         /* Put second chess program into idle state */
11486         if (second.pr != NoProc &&
11487             gameMode == TwoMachinesPlay) {
11488             SendToProgram("force\n", &second);
11489             if (second.usePing) {
11490               char buf[MSG_SIZ];
11491               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11492               SendToProgram(buf, &second);
11493             }
11494         }
11495     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11496         /* Kill off second chess program */
11497         if (second.isr != NULL)
11498           RemoveInputSource(second.isr);
11499         second.isr = NULL;
11500
11501         if (second.pr != NoProc) {
11502             DoSleep( appData.delayBeforeQuit );
11503             SendToProgram("quit\n", &second);
11504             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11505             second.reload = TRUE;
11506         }
11507         second.pr = NoProc;
11508     }
11509
11510     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11511         char resChar = '=';
11512         switch (result) {
11513         case WhiteWins:
11514           resChar = '+';
11515           if (first.twoMachinesColor[0] == 'w') {
11516             first.matchWins++;
11517           } else {
11518             second.matchWins++;
11519           }
11520           break;
11521         case BlackWins:
11522           resChar = '-';
11523           if (first.twoMachinesColor[0] == 'b') {
11524             first.matchWins++;
11525           } else {
11526             second.matchWins++;
11527           }
11528           break;
11529         case GameUnfinished:
11530           resChar = ' ';
11531         default:
11532           break;
11533         }
11534
11535         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11536         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11537             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11538             ReserveGame(nextGame, resChar); // sets nextGame
11539             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11540             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11541         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11542
11543         if (nextGame <= appData.matchGames && !abortMatch) {
11544             gameMode = nextGameMode;
11545             matchGame = nextGame; // this will be overruled in tourney mode!
11546             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11547             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11548             endingGame = 0; /* [HGM] crash */
11549             return;
11550         } else {
11551             gameMode = nextGameMode;
11552             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11553                      first.tidy, second.tidy,
11554                      first.matchWins, second.matchWins,
11555                      appData.matchGames - (first.matchWins + second.matchWins));
11556             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11557             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11558             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11559             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11560                 first.twoMachinesColor = "black\n";
11561                 second.twoMachinesColor = "white\n";
11562             } else {
11563                 first.twoMachinesColor = "white\n";
11564                 second.twoMachinesColor = "black\n";
11565             }
11566         }
11567     }
11568     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11569         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11570       ExitAnalyzeMode();
11571     gameMode = nextGameMode;
11572     ModeHighlight();
11573     endingGame = 0;  /* [HGM] crash */
11574     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11575         if(matchMode == TRUE) { // match through command line: exit with or without popup
11576             if(ranking) {
11577                 ToNrEvent(forwardMostMove);
11578                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11579                 else ExitEvent(0);
11580             } else DisplayFatalError(buf, 0, 0);
11581         } else { // match through menu; just stop, with or without popup
11582             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11583             ModeHighlight();
11584             if(ranking){
11585                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11586             } else DisplayNote(buf);
11587       }
11588       if(ranking) free(ranking);
11589     }
11590 }
11591
11592 /* Assumes program was just initialized (initString sent).
11593    Leaves program in force mode. */
11594 void
11595 FeedMovesToProgram (ChessProgramState *cps, int upto)
11596 {
11597     int i;
11598
11599     if (appData.debugMode)
11600       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11601               startedFromSetupPosition ? "position and " : "",
11602               backwardMostMove, upto, cps->which);
11603     if(currentlyInitializedVariant != gameInfo.variant) {
11604       char buf[MSG_SIZ];
11605         // [HGM] variantswitch: make engine aware of new variant
11606         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11607                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11608                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11609         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11610         SendToProgram(buf, cps);
11611         currentlyInitializedVariant = gameInfo.variant;
11612     }
11613     SendToProgram("force\n", cps);
11614     if (startedFromSetupPosition) {
11615         SendBoard(cps, backwardMostMove);
11616     if (appData.debugMode) {
11617         fprintf(debugFP, "feedMoves\n");
11618     }
11619     }
11620     for (i = backwardMostMove; i < upto; i++) {
11621         SendMoveToProgram(i, cps);
11622     }
11623 }
11624
11625
11626 int
11627 ResurrectChessProgram ()
11628 {
11629      /* The chess program may have exited.
11630         If so, restart it and feed it all the moves made so far. */
11631     static int doInit = 0;
11632
11633     if (appData.noChessProgram) return 1;
11634
11635     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11636         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11637         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11638         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11639     } else {
11640         if (first.pr != NoProc) return 1;
11641         StartChessProgram(&first);
11642     }
11643     InitChessProgram(&first, FALSE);
11644     FeedMovesToProgram(&first, currentMove);
11645
11646     if (!first.sendTime) {
11647         /* can't tell gnuchess what its clock should read,
11648            so we bow to its notion. */
11649         ResetClocks();
11650         timeRemaining[0][currentMove] = whiteTimeRemaining;
11651         timeRemaining[1][currentMove] = blackTimeRemaining;
11652     }
11653
11654     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11655                 appData.icsEngineAnalyze) && first.analysisSupport) {
11656       SendToProgram("analyze\n", &first);
11657       first.analyzing = TRUE;
11658     }
11659     return 1;
11660 }
11661
11662 /*
11663  * Button procedures
11664  */
11665 void
11666 Reset (int redraw, int init)
11667 {
11668     int i;
11669
11670     if (appData.debugMode) {
11671         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11672                 redraw, init, gameMode);
11673     }
11674     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11675     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11676     CleanupTail(); // [HGM] vari: delete any stored variations
11677     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11678     pausing = pauseExamInvalid = FALSE;
11679     startedFromSetupPosition = blackPlaysFirst = FALSE;
11680     firstMove = TRUE;
11681     whiteFlag = blackFlag = FALSE;
11682     userOfferedDraw = FALSE;
11683     hintRequested = bookRequested = FALSE;
11684     first.maybeThinking = FALSE;
11685     second.maybeThinking = FALSE;
11686     first.bookSuspend = FALSE; // [HGM] book
11687     second.bookSuspend = FALSE;
11688     thinkOutput[0] = NULLCHAR;
11689     lastHint[0] = NULLCHAR;
11690     ClearGameInfo(&gameInfo);
11691     gameInfo.variant = StringToVariant(appData.variant);
11692     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11693     ics_user_moved = ics_clock_paused = FALSE;
11694     ics_getting_history = H_FALSE;
11695     ics_gamenum = -1;
11696     white_holding[0] = black_holding[0] = NULLCHAR;
11697     ClearProgramStats();
11698     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11699
11700     ResetFrontEnd();
11701     ClearHighlights();
11702     flipView = appData.flipView;
11703     ClearPremoveHighlights();
11704     gotPremove = FALSE;
11705     alarmSounded = FALSE;
11706     killX = killY = -1; // [HGM] lion
11707
11708     GameEnds(EndOfFile, NULL, GE_PLAYER);
11709     if(appData.serverMovesName != NULL) {
11710         /* [HGM] prepare to make moves file for broadcasting */
11711         clock_t t = clock();
11712         if(serverMoves != NULL) fclose(serverMoves);
11713         serverMoves = fopen(appData.serverMovesName, "r");
11714         if(serverMoves != NULL) {
11715             fclose(serverMoves);
11716             /* delay 15 sec before overwriting, so all clients can see end */
11717             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11718         }
11719         serverMoves = fopen(appData.serverMovesName, "w");
11720     }
11721
11722     ExitAnalyzeMode();
11723     gameMode = BeginningOfGame;
11724     ModeHighlight();
11725     if(appData.icsActive) gameInfo.variant = VariantNormal;
11726     currentMove = forwardMostMove = backwardMostMove = 0;
11727     MarkTargetSquares(1);
11728     InitPosition(redraw);
11729     for (i = 0; i < MAX_MOVES; i++) {
11730         if (commentList[i] != NULL) {
11731             free(commentList[i]);
11732             commentList[i] = NULL;
11733         }
11734     }
11735     ResetClocks();
11736     timeRemaining[0][0] = whiteTimeRemaining;
11737     timeRemaining[1][0] = blackTimeRemaining;
11738
11739     if (first.pr == NoProc) {
11740         StartChessProgram(&first);
11741     }
11742     if (init) {
11743             InitChessProgram(&first, startedFromSetupPosition);
11744     }
11745     DisplayTitle("");
11746     DisplayMessage("", "");
11747     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11748     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11749     ClearMap();        // [HGM] exclude: invalidate map
11750 }
11751
11752 void
11753 AutoPlayGameLoop ()
11754 {
11755     for (;;) {
11756         if (!AutoPlayOneMove())
11757           return;
11758         if (matchMode || appData.timeDelay == 0)
11759           continue;
11760         if (appData.timeDelay < 0)
11761           return;
11762         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11763         break;
11764     }
11765 }
11766
11767 void
11768 AnalyzeNextGame()
11769 {
11770     ReloadGame(1); // next game
11771 }
11772
11773 int
11774 AutoPlayOneMove ()
11775 {
11776     int fromX, fromY, toX, toY;
11777
11778     if (appData.debugMode) {
11779       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11780     }
11781
11782     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11783       return FALSE;
11784
11785     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11786       pvInfoList[currentMove].depth = programStats.depth;
11787       pvInfoList[currentMove].score = programStats.score;
11788       pvInfoList[currentMove].time  = 0;
11789       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11790       else { // append analysis of final position as comment
11791         char buf[MSG_SIZ];
11792         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11793         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11794       }
11795       programStats.depth = 0;
11796     }
11797
11798     if (currentMove >= forwardMostMove) {
11799       if(gameMode == AnalyzeFile) {
11800           if(appData.loadGameIndex == -1) {
11801             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11802           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11803           } else {
11804           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11805         }
11806       }
11807 //      gameMode = EndOfGame;
11808 //      ModeHighlight();
11809
11810       /* [AS] Clear current move marker at the end of a game */
11811       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11812
11813       return FALSE;
11814     }
11815
11816     toX = moveList[currentMove][2] - AAA;
11817     toY = moveList[currentMove][3] - ONE;
11818
11819     if (moveList[currentMove][1] == '@') {
11820         if (appData.highlightLastMove) {
11821             SetHighlights(-1, -1, toX, toY);
11822         }
11823     } else {
11824         int viaX = moveList[currentMove][5] - AAA;
11825         int viaY = moveList[currentMove][6] - ONE;
11826         fromX = moveList[currentMove][0] - AAA;
11827         fromY = moveList[currentMove][1] - ONE;
11828
11829         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11830
11831         if(moveList[currentMove][4] == ';') { // multi-leg
11832             ChessSquare piece = boards[currentMove][viaY][viaX];
11833             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11834             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11835             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11836             boards[currentMove][viaY][viaX] = piece;
11837         } else
11838         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11839
11840         if (appData.highlightLastMove) {
11841             SetHighlights(fromX, fromY, toX, toY);
11842         }
11843     }
11844     DisplayMove(currentMove);
11845     SendMoveToProgram(currentMove++, &first);
11846     DisplayBothClocks();
11847     DrawPosition(FALSE, boards[currentMove]);
11848     // [HGM] PV info: always display, routine tests if empty
11849     DisplayComment(currentMove - 1, commentList[currentMove]);
11850     return TRUE;
11851 }
11852
11853
11854 int
11855 LoadGameOneMove (ChessMove readAhead)
11856 {
11857     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11858     char promoChar = NULLCHAR;
11859     ChessMove moveType;
11860     char move[MSG_SIZ];
11861     char *p, *q;
11862
11863     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11864         gameMode != AnalyzeMode && gameMode != Training) {
11865         gameFileFP = NULL;
11866         return FALSE;
11867     }
11868
11869     yyboardindex = forwardMostMove;
11870     if (readAhead != EndOfFile) {
11871       moveType = readAhead;
11872     } else {
11873       if (gameFileFP == NULL)
11874           return FALSE;
11875       moveType = (ChessMove) Myylex();
11876     }
11877
11878     done = FALSE;
11879     switch (moveType) {
11880       case Comment:
11881         if (appData.debugMode)
11882           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11883         p = yy_text;
11884
11885         /* append the comment but don't display it */
11886         AppendComment(currentMove, p, FALSE);
11887         return TRUE;
11888
11889       case WhiteCapturesEnPassant:
11890       case BlackCapturesEnPassant:
11891       case WhitePromotion:
11892       case BlackPromotion:
11893       case WhiteNonPromotion:
11894       case BlackNonPromotion:
11895       case NormalMove:
11896       case FirstLeg:
11897       case WhiteKingSideCastle:
11898       case WhiteQueenSideCastle:
11899       case BlackKingSideCastle:
11900       case BlackQueenSideCastle:
11901       case WhiteKingSideCastleWild:
11902       case WhiteQueenSideCastleWild:
11903       case BlackKingSideCastleWild:
11904       case BlackQueenSideCastleWild:
11905       /* PUSH Fabien */
11906       case WhiteHSideCastleFR:
11907       case WhiteASideCastleFR:
11908       case BlackHSideCastleFR:
11909       case BlackASideCastleFR:
11910       /* POP Fabien */
11911         if (appData.debugMode)
11912           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11913         fromX = currentMoveString[0] - AAA;
11914         fromY = currentMoveString[1] - ONE;
11915         toX = currentMoveString[2] - AAA;
11916         toY = currentMoveString[3] - ONE;
11917         promoChar = currentMoveString[4];
11918         if(promoChar == ';') promoChar = NULLCHAR;
11919         break;
11920
11921       case WhiteDrop:
11922       case BlackDrop:
11923         if (appData.debugMode)
11924           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11925         fromX = moveType == WhiteDrop ?
11926           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11927         (int) CharToPiece(ToLower(currentMoveString[0]));
11928         fromY = DROP_RANK;
11929         toX = currentMoveString[2] - AAA;
11930         toY = currentMoveString[3] - ONE;
11931         break;
11932
11933       case WhiteWins:
11934       case BlackWins:
11935       case GameIsDrawn:
11936       case GameUnfinished:
11937         if (appData.debugMode)
11938           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11939         p = strchr(yy_text, '{');
11940         if (p == NULL) p = strchr(yy_text, '(');
11941         if (p == NULL) {
11942             p = yy_text;
11943             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11944         } else {
11945             q = strchr(p, *p == '{' ? '}' : ')');
11946             if (q != NULL) *q = NULLCHAR;
11947             p++;
11948         }
11949         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11950         GameEnds(moveType, p, GE_FILE);
11951         done = TRUE;
11952         if (cmailMsgLoaded) {
11953             ClearHighlights();
11954             flipView = WhiteOnMove(currentMove);
11955             if (moveType == GameUnfinished) flipView = !flipView;
11956             if (appData.debugMode)
11957               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11958         }
11959         break;
11960
11961       case EndOfFile:
11962         if (appData.debugMode)
11963           fprintf(debugFP, "Parser hit end of file\n");
11964         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11965           case MT_NONE:
11966           case MT_CHECK:
11967             break;
11968           case MT_CHECKMATE:
11969           case MT_STAINMATE:
11970             if (WhiteOnMove(currentMove)) {
11971                 GameEnds(BlackWins, "Black mates", GE_FILE);
11972             } else {
11973                 GameEnds(WhiteWins, "White mates", GE_FILE);
11974             }
11975             break;
11976           case MT_STALEMATE:
11977             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11978             break;
11979         }
11980         done = TRUE;
11981         break;
11982
11983       case MoveNumberOne:
11984         if (lastLoadGameStart == GNUChessGame) {
11985             /* GNUChessGames have numbers, but they aren't move numbers */
11986             if (appData.debugMode)
11987               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11988                       yy_text, (int) moveType);
11989             return LoadGameOneMove(EndOfFile); /* tail recursion */
11990         }
11991         /* else fall thru */
11992
11993       case XBoardGame:
11994       case GNUChessGame:
11995       case PGNTag:
11996         /* Reached start of next game in file */
11997         if (appData.debugMode)
11998           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11999         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12000           case MT_NONE:
12001           case MT_CHECK:
12002             break;
12003           case MT_CHECKMATE:
12004           case MT_STAINMATE:
12005             if (WhiteOnMove(currentMove)) {
12006                 GameEnds(BlackWins, "Black mates", GE_FILE);
12007             } else {
12008                 GameEnds(WhiteWins, "White mates", GE_FILE);
12009             }
12010             break;
12011           case MT_STALEMATE:
12012             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12013             break;
12014         }
12015         done = TRUE;
12016         break;
12017
12018       case PositionDiagram:     /* should not happen; ignore */
12019       case ElapsedTime:         /* ignore */
12020       case NAG:                 /* ignore */
12021         if (appData.debugMode)
12022           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12023                   yy_text, (int) moveType);
12024         return LoadGameOneMove(EndOfFile); /* tail recursion */
12025
12026       case IllegalMove:
12027         if (appData.testLegality) {
12028             if (appData.debugMode)
12029               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12030             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12031                     (forwardMostMove / 2) + 1,
12032                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12033             DisplayError(move, 0);
12034             done = TRUE;
12035         } else {
12036             if (appData.debugMode)
12037               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12038                       yy_text, currentMoveString);
12039             fromX = currentMoveString[0] - AAA;
12040             fromY = currentMoveString[1] - ONE;
12041             toX = currentMoveString[2] - AAA;
12042             toY = currentMoveString[3] - ONE;
12043             promoChar = currentMoveString[4];
12044         }
12045         break;
12046
12047       case AmbiguousMove:
12048         if (appData.debugMode)
12049           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12050         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12051                 (forwardMostMove / 2) + 1,
12052                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12053         DisplayError(move, 0);
12054         done = TRUE;
12055         break;
12056
12057       default:
12058       case ImpossibleMove:
12059         if (appData.debugMode)
12060           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12061         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12062                 (forwardMostMove / 2) + 1,
12063                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12064         DisplayError(move, 0);
12065         done = TRUE;
12066         break;
12067     }
12068
12069     if (done) {
12070         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12071             DrawPosition(FALSE, boards[currentMove]);
12072             DisplayBothClocks();
12073             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12074               DisplayComment(currentMove - 1, commentList[currentMove]);
12075         }
12076         (void) StopLoadGameTimer();
12077         gameFileFP = NULL;
12078         cmailOldMove = forwardMostMove;
12079         return FALSE;
12080     } else {
12081         /* currentMoveString is set as a side-effect of yylex */
12082
12083         thinkOutput[0] = NULLCHAR;
12084         MakeMove(fromX, fromY, toX, toY, promoChar);
12085         killX = killY = -1; // [HGM] lion: used up
12086         currentMove = forwardMostMove;
12087         return TRUE;
12088     }
12089 }
12090
12091 /* Load the nth game from the given file */
12092 int
12093 LoadGameFromFile (char *filename, int n, char *title, int useList)
12094 {
12095     FILE *f;
12096     char buf[MSG_SIZ];
12097
12098     if (strcmp(filename, "-") == 0) {
12099         f = stdin;
12100         title = "stdin";
12101     } else {
12102         f = fopen(filename, "rb");
12103         if (f == NULL) {
12104           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12105             DisplayError(buf, errno);
12106             return FALSE;
12107         }
12108     }
12109     if (fseek(f, 0, 0) == -1) {
12110         /* f is not seekable; probably a pipe */
12111         useList = FALSE;
12112     }
12113     if (useList && n == 0) {
12114         int error = GameListBuild(f);
12115         if (error) {
12116             DisplayError(_("Cannot build game list"), error);
12117         } else if (!ListEmpty(&gameList) &&
12118                    ((ListGame *) gameList.tailPred)->number > 1) {
12119             GameListPopUp(f, title);
12120             return TRUE;
12121         }
12122         GameListDestroy();
12123         n = 1;
12124     }
12125     if (n == 0) n = 1;
12126     return LoadGame(f, n, title, FALSE);
12127 }
12128
12129
12130 void
12131 MakeRegisteredMove ()
12132 {
12133     int fromX, fromY, toX, toY;
12134     char promoChar;
12135     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12136         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12137           case CMAIL_MOVE:
12138           case CMAIL_DRAW:
12139             if (appData.debugMode)
12140               fprintf(debugFP, "Restoring %s for game %d\n",
12141                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12142
12143             thinkOutput[0] = NULLCHAR;
12144             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12145             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12146             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12147             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12148             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12149             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12150             MakeMove(fromX, fromY, toX, toY, promoChar);
12151             ShowMove(fromX, fromY, toX, toY);
12152
12153             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12154               case MT_NONE:
12155               case MT_CHECK:
12156                 break;
12157
12158               case MT_CHECKMATE:
12159               case MT_STAINMATE:
12160                 if (WhiteOnMove(currentMove)) {
12161                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12162                 } else {
12163                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12164                 }
12165                 break;
12166
12167               case MT_STALEMATE:
12168                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12169                 break;
12170             }
12171
12172             break;
12173
12174           case CMAIL_RESIGN:
12175             if (WhiteOnMove(currentMove)) {
12176                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12177             } else {
12178                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12179             }
12180             break;
12181
12182           case CMAIL_ACCEPT:
12183             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12184             break;
12185
12186           default:
12187             break;
12188         }
12189     }
12190
12191     return;
12192 }
12193
12194 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12195 int
12196 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12197 {
12198     int retVal;
12199
12200     if (gameNumber > nCmailGames) {
12201         DisplayError(_("No more games in this message"), 0);
12202         return FALSE;
12203     }
12204     if (f == lastLoadGameFP) {
12205         int offset = gameNumber - lastLoadGameNumber;
12206         if (offset == 0) {
12207             cmailMsg[0] = NULLCHAR;
12208             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12209                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12210                 nCmailMovesRegistered--;
12211             }
12212             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12213             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12214                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12215             }
12216         } else {
12217             if (! RegisterMove()) return FALSE;
12218         }
12219     }
12220
12221     retVal = LoadGame(f, gameNumber, title, useList);
12222
12223     /* Make move registered during previous look at this game, if any */
12224     MakeRegisteredMove();
12225
12226     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12227         commentList[currentMove]
12228           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12229         DisplayComment(currentMove - 1, commentList[currentMove]);
12230     }
12231
12232     return retVal;
12233 }
12234
12235 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12236 int
12237 ReloadGame (int offset)
12238 {
12239     int gameNumber = lastLoadGameNumber + offset;
12240     if (lastLoadGameFP == NULL) {
12241         DisplayError(_("No game has been loaded yet"), 0);
12242         return FALSE;
12243     }
12244     if (gameNumber <= 0) {
12245         DisplayError(_("Can't back up any further"), 0);
12246         return FALSE;
12247     }
12248     if (cmailMsgLoaded) {
12249         return CmailLoadGame(lastLoadGameFP, gameNumber,
12250                              lastLoadGameTitle, lastLoadGameUseList);
12251     } else {
12252         return LoadGame(lastLoadGameFP, gameNumber,
12253                         lastLoadGameTitle, lastLoadGameUseList);
12254     }
12255 }
12256
12257 int keys[EmptySquare+1];
12258
12259 int
12260 PositionMatches (Board b1, Board b2)
12261 {
12262     int r, f, sum=0;
12263     switch(appData.searchMode) {
12264         case 1: return CompareWithRights(b1, b2);
12265         case 2:
12266             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12267                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12268             }
12269             return TRUE;
12270         case 3:
12271             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12272               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12273                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12274             }
12275             return sum==0;
12276         case 4:
12277             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12278                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12279             }
12280             return sum==0;
12281     }
12282     return TRUE;
12283 }
12284
12285 #define Q_PROMO  4
12286 #define Q_EP     3
12287 #define Q_BCASTL 2
12288 #define Q_WCASTL 1
12289
12290 int pieceList[256], quickBoard[256];
12291 ChessSquare pieceType[256] = { EmptySquare };
12292 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12293 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12294 int soughtTotal, turn;
12295 Boolean epOK, flipSearch;
12296
12297 typedef struct {
12298     unsigned char piece, to;
12299 } Move;
12300
12301 #define DSIZE (250000)
12302
12303 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12304 Move *moveDatabase = initialSpace;
12305 unsigned int movePtr, dataSize = DSIZE;
12306
12307 int
12308 MakePieceList (Board board, int *counts)
12309 {
12310     int r, f, n=Q_PROMO, total=0;
12311     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12312     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12313         int sq = f + (r<<4);
12314         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12315             quickBoard[sq] = ++n;
12316             pieceList[n] = sq;
12317             pieceType[n] = board[r][f];
12318             counts[board[r][f]]++;
12319             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12320             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12321             total++;
12322         }
12323     }
12324     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12325     return total;
12326 }
12327
12328 void
12329 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12330 {
12331     int sq = fromX + (fromY<<4);
12332     int piece = quickBoard[sq], rook;
12333     quickBoard[sq] = 0;
12334     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12335     if(piece == pieceList[1] && fromY == toY) {
12336       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12337         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12338         moveDatabase[movePtr++].piece = Q_WCASTL;
12339         quickBoard[sq] = piece;
12340         piece = quickBoard[from]; quickBoard[from] = 0;
12341         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12342       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12343         quickBoard[sq] = 0; // remove Rook
12344         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12345         moveDatabase[movePtr++].piece = Q_WCASTL;
12346         quickBoard[sq] = pieceList[1]; // put King
12347         piece = rook;
12348         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12349       }
12350     } else
12351     if(piece == pieceList[2] && fromY == toY) {
12352       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12353         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12354         moveDatabase[movePtr++].piece = Q_BCASTL;
12355         quickBoard[sq] = piece;
12356         piece = quickBoard[from]; quickBoard[from] = 0;
12357         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12358       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12359         quickBoard[sq] = 0; // remove Rook
12360         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12361         moveDatabase[movePtr++].piece = Q_BCASTL;
12362         quickBoard[sq] = pieceList[2]; // put King
12363         piece = rook;
12364         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12365       }
12366     } else
12367     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12368         quickBoard[(fromY<<4)+toX] = 0;
12369         moveDatabase[movePtr].piece = Q_EP;
12370         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12371         moveDatabase[movePtr].to = sq;
12372     } else
12373     if(promoPiece != pieceType[piece]) {
12374         moveDatabase[movePtr++].piece = Q_PROMO;
12375         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12376     }
12377     moveDatabase[movePtr].piece = piece;
12378     quickBoard[sq] = piece;
12379     movePtr++;
12380 }
12381
12382 int
12383 PackGame (Board board)
12384 {
12385     Move *newSpace = NULL;
12386     moveDatabase[movePtr].piece = 0; // terminate previous game
12387     if(movePtr > dataSize) {
12388         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12389         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12390         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12391         if(newSpace) {
12392             int i;
12393             Move *p = moveDatabase, *q = newSpace;
12394             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12395             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12396             moveDatabase = newSpace;
12397         } else { // calloc failed, we must be out of memory. Too bad...
12398             dataSize = 0; // prevent calloc events for all subsequent games
12399             return 0;     // and signal this one isn't cached
12400         }
12401     }
12402     movePtr++;
12403     MakePieceList(board, counts);
12404     return movePtr;
12405 }
12406
12407 int
12408 QuickCompare (Board board, int *minCounts, int *maxCounts)
12409 {   // compare according to search mode
12410     int r, f;
12411     switch(appData.searchMode)
12412     {
12413       case 1: // exact position match
12414         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12415         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12416             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12417         }
12418         break;
12419       case 2: // can have extra material on empty squares
12420         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12421             if(board[r][f] == EmptySquare) continue;
12422             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12423         }
12424         break;
12425       case 3: // material with exact Pawn structure
12426         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12427             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12428             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12429         } // fall through to material comparison
12430       case 4: // exact material
12431         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12432         break;
12433       case 6: // material range with given imbalance
12434         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12435         // fall through to range comparison
12436       case 5: // material range
12437         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12438     }
12439     return TRUE;
12440 }
12441
12442 int
12443 QuickScan (Board board, Move *move)
12444 {   // reconstruct game,and compare all positions in it
12445     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12446     do {
12447         int piece = move->piece;
12448         int to = move->to, from = pieceList[piece];
12449         if(found < 0) { // if already found just scan to game end for final piece count
12450           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12451            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12452            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12453                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12454             ) {
12455             static int lastCounts[EmptySquare+1];
12456             int i;
12457             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12458             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12459           } else stretch = 0;
12460           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12461           if(found >= 0 && !appData.minPieces) return found;
12462         }
12463         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12464           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12465           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12466             piece = (++move)->piece;
12467             from = pieceList[piece];
12468             counts[pieceType[piece]]--;
12469             pieceType[piece] = (ChessSquare) move->to;
12470             counts[move->to]++;
12471           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12472             counts[pieceType[quickBoard[to]]]--;
12473             quickBoard[to] = 0; total--;
12474             move++;
12475             continue;
12476           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12477             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12478             from  = pieceList[piece]; // so this must be King
12479             quickBoard[from] = 0;
12480             pieceList[piece] = to;
12481             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12482             quickBoard[from] = 0; // rook
12483             quickBoard[to] = piece;
12484             to = move->to; piece = move->piece;
12485             goto aftercastle;
12486           }
12487         }
12488         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12489         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12490         quickBoard[from] = 0;
12491       aftercastle:
12492         quickBoard[to] = piece;
12493         pieceList[piece] = to;
12494         cnt++; turn ^= 3;
12495         move++;
12496     } while(1);
12497 }
12498
12499 void
12500 InitSearch ()
12501 {
12502     int r, f;
12503     flipSearch = FALSE;
12504     CopyBoard(soughtBoard, boards[currentMove]);
12505     soughtTotal = MakePieceList(soughtBoard, maxSought);
12506     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12507     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12508     CopyBoard(reverseBoard, boards[currentMove]);
12509     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12510         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12511         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12512         reverseBoard[r][f] = piece;
12513     }
12514     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12515     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12516     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12517                  || (boards[currentMove][CASTLING][2] == NoRights ||
12518                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12519                  && (boards[currentMove][CASTLING][5] == NoRights ||
12520                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12521       ) {
12522         flipSearch = TRUE;
12523         CopyBoard(flipBoard, soughtBoard);
12524         CopyBoard(rotateBoard, reverseBoard);
12525         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12526             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12527             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12528         }
12529     }
12530     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12531     if(appData.searchMode >= 5) {
12532         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12533         MakePieceList(soughtBoard, minSought);
12534         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12535     }
12536     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12537         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12538 }
12539
12540 GameInfo dummyInfo;
12541 static int creatingBook;
12542
12543 int
12544 GameContainsPosition (FILE *f, ListGame *lg)
12545 {
12546     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12547     int fromX, fromY, toX, toY;
12548     char promoChar;
12549     static int initDone=FALSE;
12550
12551     // weed out games based on numerical tag comparison
12552     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12553     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12554     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12555     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12556     if(!initDone) {
12557         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12558         initDone = TRUE;
12559     }
12560     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12561     else CopyBoard(boards[scratch], initialPosition); // default start position
12562     if(lg->moves) {
12563         turn = btm + 1;
12564         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12565         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12566     }
12567     if(btm) plyNr++;
12568     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12569     fseek(f, lg->offset, 0);
12570     yynewfile(f);
12571     while(1) {
12572         yyboardindex = scratch;
12573         quickFlag = plyNr+1;
12574         next = Myylex();
12575         quickFlag = 0;
12576         switch(next) {
12577             case PGNTag:
12578                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12579             default:
12580                 continue;
12581
12582             case XBoardGame:
12583             case GNUChessGame:
12584                 if(plyNr) return -1; // after we have seen moves, this is for new game
12585               continue;
12586
12587             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12588             case ImpossibleMove:
12589             case WhiteWins: // game ends here with these four
12590             case BlackWins:
12591             case GameIsDrawn:
12592             case GameUnfinished:
12593                 return -1;
12594
12595             case IllegalMove:
12596                 if(appData.testLegality) return -1;
12597             case WhiteCapturesEnPassant:
12598             case BlackCapturesEnPassant:
12599             case WhitePromotion:
12600             case BlackPromotion:
12601             case WhiteNonPromotion:
12602             case BlackNonPromotion:
12603             case NormalMove:
12604             case FirstLeg:
12605             case WhiteKingSideCastle:
12606             case WhiteQueenSideCastle:
12607             case BlackKingSideCastle:
12608             case BlackQueenSideCastle:
12609             case WhiteKingSideCastleWild:
12610             case WhiteQueenSideCastleWild:
12611             case BlackKingSideCastleWild:
12612             case BlackQueenSideCastleWild:
12613             case WhiteHSideCastleFR:
12614             case WhiteASideCastleFR:
12615             case BlackHSideCastleFR:
12616             case BlackASideCastleFR:
12617                 fromX = currentMoveString[0] - AAA;
12618                 fromY = currentMoveString[1] - ONE;
12619                 toX = currentMoveString[2] - AAA;
12620                 toY = currentMoveString[3] - ONE;
12621                 promoChar = currentMoveString[4];
12622                 break;
12623             case WhiteDrop:
12624             case BlackDrop:
12625                 fromX = next == WhiteDrop ?
12626                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12627                   (int) CharToPiece(ToLower(currentMoveString[0]));
12628                 fromY = DROP_RANK;
12629                 toX = currentMoveString[2] - AAA;
12630                 toY = currentMoveString[3] - ONE;
12631                 promoChar = 0;
12632                 break;
12633         }
12634         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12635         plyNr++;
12636         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12637         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12638         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12639         if(appData.findMirror) {
12640             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12641             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12642         }
12643     }
12644 }
12645
12646 /* Load the nth game from open file f */
12647 int
12648 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12649 {
12650     ChessMove cm;
12651     char buf[MSG_SIZ];
12652     int gn = gameNumber;
12653     ListGame *lg = NULL;
12654     int numPGNTags = 0;
12655     int err, pos = -1;
12656     GameMode oldGameMode;
12657     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12658
12659     if (appData.debugMode)
12660         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12661
12662     if (gameMode == Training )
12663         SetTrainingModeOff();
12664
12665     oldGameMode = gameMode;
12666     if (gameMode != BeginningOfGame) {
12667       Reset(FALSE, TRUE);
12668     }
12669     killX = killY = -1; // [HGM] lion: in case we did not Reset
12670
12671     gameFileFP = f;
12672     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12673         fclose(lastLoadGameFP);
12674     }
12675
12676     if (useList) {
12677         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12678
12679         if (lg) {
12680             fseek(f, lg->offset, 0);
12681             GameListHighlight(gameNumber);
12682             pos = lg->position;
12683             gn = 1;
12684         }
12685         else {
12686             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12687               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12688             else
12689             DisplayError(_("Game number out of range"), 0);
12690             return FALSE;
12691         }
12692     } else {
12693         GameListDestroy();
12694         if (fseek(f, 0, 0) == -1) {
12695             if (f == lastLoadGameFP ?
12696                 gameNumber == lastLoadGameNumber + 1 :
12697                 gameNumber == 1) {
12698                 gn = 1;
12699             } else {
12700                 DisplayError(_("Can't seek on game file"), 0);
12701                 return FALSE;
12702             }
12703         }
12704     }
12705     lastLoadGameFP = f;
12706     lastLoadGameNumber = gameNumber;
12707     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12708     lastLoadGameUseList = useList;
12709
12710     yynewfile(f);
12711
12712     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12713       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12714                 lg->gameInfo.black);
12715             DisplayTitle(buf);
12716     } else if (*title != NULLCHAR) {
12717         if (gameNumber > 1) {
12718           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12719             DisplayTitle(buf);
12720         } else {
12721             DisplayTitle(title);
12722         }
12723     }
12724
12725     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12726         gameMode = PlayFromGameFile;
12727         ModeHighlight();
12728     }
12729
12730     currentMove = forwardMostMove = backwardMostMove = 0;
12731     CopyBoard(boards[0], initialPosition);
12732     StopClocks();
12733
12734     /*
12735      * Skip the first gn-1 games in the file.
12736      * Also skip over anything that precedes an identifiable
12737      * start of game marker, to avoid being confused by
12738      * garbage at the start of the file.  Currently
12739      * recognized start of game markers are the move number "1",
12740      * the pattern "gnuchess .* game", the pattern
12741      * "^[#;%] [^ ]* game file", and a PGN tag block.
12742      * A game that starts with one of the latter two patterns
12743      * will also have a move number 1, possibly
12744      * following a position diagram.
12745      * 5-4-02: Let's try being more lenient and allowing a game to
12746      * start with an unnumbered move.  Does that break anything?
12747      */
12748     cm = lastLoadGameStart = EndOfFile;
12749     while (gn > 0) {
12750         yyboardindex = forwardMostMove;
12751         cm = (ChessMove) Myylex();
12752         switch (cm) {
12753           case EndOfFile:
12754             if (cmailMsgLoaded) {
12755                 nCmailGames = CMAIL_MAX_GAMES - gn;
12756             } else {
12757                 Reset(TRUE, TRUE);
12758                 DisplayError(_("Game not found in file"), 0);
12759             }
12760             return FALSE;
12761
12762           case GNUChessGame:
12763           case XBoardGame:
12764             gn--;
12765             lastLoadGameStart = cm;
12766             break;
12767
12768           case MoveNumberOne:
12769             switch (lastLoadGameStart) {
12770               case GNUChessGame:
12771               case XBoardGame:
12772               case PGNTag:
12773                 break;
12774               case MoveNumberOne:
12775               case EndOfFile:
12776                 gn--;           /* count this game */
12777                 lastLoadGameStart = cm;
12778                 break;
12779               default:
12780                 /* impossible */
12781                 break;
12782             }
12783             break;
12784
12785           case PGNTag:
12786             switch (lastLoadGameStart) {
12787               case GNUChessGame:
12788               case PGNTag:
12789               case MoveNumberOne:
12790               case EndOfFile:
12791                 gn--;           /* count this game */
12792                 lastLoadGameStart = cm;
12793                 break;
12794               case XBoardGame:
12795                 lastLoadGameStart = cm; /* game counted already */
12796                 break;
12797               default:
12798                 /* impossible */
12799                 break;
12800             }
12801             if (gn > 0) {
12802                 do {
12803                     yyboardindex = forwardMostMove;
12804                     cm = (ChessMove) Myylex();
12805                 } while (cm == PGNTag || cm == Comment);
12806             }
12807             break;
12808
12809           case WhiteWins:
12810           case BlackWins:
12811           case GameIsDrawn:
12812             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12813                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12814                     != CMAIL_OLD_RESULT) {
12815                     nCmailResults ++ ;
12816                     cmailResult[  CMAIL_MAX_GAMES
12817                                 - gn - 1] = CMAIL_OLD_RESULT;
12818                 }
12819             }
12820             break;
12821
12822           case NormalMove:
12823           case FirstLeg:
12824             /* Only a NormalMove can be at the start of a game
12825              * without a position diagram. */
12826             if (lastLoadGameStart == EndOfFile ) {
12827               gn--;
12828               lastLoadGameStart = MoveNumberOne;
12829             }
12830             break;
12831
12832           default:
12833             break;
12834         }
12835     }
12836
12837     if (appData.debugMode)
12838       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12839
12840     if (cm == XBoardGame) {
12841         /* Skip any header junk before position diagram and/or move 1 */
12842         for (;;) {
12843             yyboardindex = forwardMostMove;
12844             cm = (ChessMove) Myylex();
12845
12846             if (cm == EndOfFile ||
12847                 cm == GNUChessGame || cm == XBoardGame) {
12848                 /* Empty game; pretend end-of-file and handle later */
12849                 cm = EndOfFile;
12850                 break;
12851             }
12852
12853             if (cm == MoveNumberOne || cm == PositionDiagram ||
12854                 cm == PGNTag || cm == Comment)
12855               break;
12856         }
12857     } else if (cm == GNUChessGame) {
12858         if (gameInfo.event != NULL) {
12859             free(gameInfo.event);
12860         }
12861         gameInfo.event = StrSave(yy_text);
12862     }
12863
12864     startedFromSetupPosition = FALSE;
12865     while (cm == PGNTag) {
12866         if (appData.debugMode)
12867           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12868         err = ParsePGNTag(yy_text, &gameInfo);
12869         if (!err) numPGNTags++;
12870
12871         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12872         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
12873             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12874             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12875             InitPosition(TRUE);
12876             oldVariant = gameInfo.variant;
12877             if (appData.debugMode)
12878               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12879         }
12880
12881
12882         if (gameInfo.fen != NULL) {
12883           Board initial_position;
12884           startedFromSetupPosition = TRUE;
12885           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12886             Reset(TRUE, TRUE);
12887             DisplayError(_("Bad FEN position in file"), 0);
12888             return FALSE;
12889           }
12890           CopyBoard(boards[0], initial_position);
12891           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
12892             CopyBoard(initialPosition, initial_position);
12893           if (blackPlaysFirst) {
12894             currentMove = forwardMostMove = backwardMostMove = 1;
12895             CopyBoard(boards[1], initial_position);
12896             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12897             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12898             timeRemaining[0][1] = whiteTimeRemaining;
12899             timeRemaining[1][1] = blackTimeRemaining;
12900             if (commentList[0] != NULL) {
12901               commentList[1] = commentList[0];
12902               commentList[0] = NULL;
12903             }
12904           } else {
12905             currentMove = forwardMostMove = backwardMostMove = 0;
12906           }
12907           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12908           {   int i;
12909               initialRulePlies = FENrulePlies;
12910               for( i=0; i< nrCastlingRights; i++ )
12911                   initialRights[i] = initial_position[CASTLING][i];
12912           }
12913           yyboardindex = forwardMostMove;
12914           free(gameInfo.fen);
12915           gameInfo.fen = NULL;
12916         }
12917
12918         yyboardindex = forwardMostMove;
12919         cm = (ChessMove) Myylex();
12920
12921         /* Handle comments interspersed among the tags */
12922         while (cm == Comment) {
12923             char *p;
12924             if (appData.debugMode)
12925               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12926             p = yy_text;
12927             AppendComment(currentMove, p, FALSE);
12928             yyboardindex = forwardMostMove;
12929             cm = (ChessMove) Myylex();
12930         }
12931     }
12932
12933     /* don't rely on existence of Event tag since if game was
12934      * pasted from clipboard the Event tag may not exist
12935      */
12936     if (numPGNTags > 0){
12937         char *tags;
12938         if (gameInfo.variant == VariantNormal) {
12939           VariantClass v = StringToVariant(gameInfo.event);
12940           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12941           if(v < VariantShogi) gameInfo.variant = v;
12942         }
12943         if (!matchMode) {
12944           if( appData.autoDisplayTags ) {
12945             tags = PGNTags(&gameInfo);
12946             TagsPopUp(tags, CmailMsg());
12947             free(tags);
12948           }
12949         }
12950     } else {
12951         /* Make something up, but don't display it now */
12952         SetGameInfo();
12953         TagsPopDown();
12954     }
12955
12956     if (cm == PositionDiagram) {
12957         int i, j;
12958         char *p;
12959         Board initial_position;
12960
12961         if (appData.debugMode)
12962           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12963
12964         if (!startedFromSetupPosition) {
12965             p = yy_text;
12966             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12967               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12968                 switch (*p) {
12969                   case '{':
12970                   case '[':
12971                   case '-':
12972                   case ' ':
12973                   case '\t':
12974                   case '\n':
12975                   case '\r':
12976                     break;
12977                   default:
12978                     initial_position[i][j++] = CharToPiece(*p);
12979                     break;
12980                 }
12981             while (*p == ' ' || *p == '\t' ||
12982                    *p == '\n' || *p == '\r') p++;
12983
12984             if (strncmp(p, "black", strlen("black"))==0)
12985               blackPlaysFirst = TRUE;
12986             else
12987               blackPlaysFirst = FALSE;
12988             startedFromSetupPosition = TRUE;
12989
12990             CopyBoard(boards[0], initial_position);
12991             if (blackPlaysFirst) {
12992                 currentMove = forwardMostMove = backwardMostMove = 1;
12993                 CopyBoard(boards[1], initial_position);
12994                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12995                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12996                 timeRemaining[0][1] = whiteTimeRemaining;
12997                 timeRemaining[1][1] = blackTimeRemaining;
12998                 if (commentList[0] != NULL) {
12999                     commentList[1] = commentList[0];
13000                     commentList[0] = NULL;
13001                 }
13002             } else {
13003                 currentMove = forwardMostMove = backwardMostMove = 0;
13004             }
13005         }
13006         yyboardindex = forwardMostMove;
13007         cm = (ChessMove) Myylex();
13008     }
13009
13010   if(!creatingBook) {
13011     if (first.pr == NoProc) {
13012         StartChessProgram(&first);
13013     }
13014     InitChessProgram(&first, FALSE);
13015     SendToProgram("force\n", &first);
13016     if (startedFromSetupPosition) {
13017         SendBoard(&first, forwardMostMove);
13018     if (appData.debugMode) {
13019         fprintf(debugFP, "Load Game\n");
13020     }
13021         DisplayBothClocks();
13022     }
13023   }
13024
13025     /* [HGM] server: flag to write setup moves in broadcast file as one */
13026     loadFlag = appData.suppressLoadMoves;
13027
13028     while (cm == Comment) {
13029         char *p;
13030         if (appData.debugMode)
13031           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13032         p = yy_text;
13033         AppendComment(currentMove, p, FALSE);
13034         yyboardindex = forwardMostMove;
13035         cm = (ChessMove) Myylex();
13036     }
13037
13038     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13039         cm == WhiteWins || cm == BlackWins ||
13040         cm == GameIsDrawn || cm == GameUnfinished) {
13041         DisplayMessage("", _("No moves in game"));
13042         if (cmailMsgLoaded) {
13043             if (appData.debugMode)
13044               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13045             ClearHighlights();
13046             flipView = FALSE;
13047         }
13048         DrawPosition(FALSE, boards[currentMove]);
13049         DisplayBothClocks();
13050         gameMode = EditGame;
13051         ModeHighlight();
13052         gameFileFP = NULL;
13053         cmailOldMove = 0;
13054         return TRUE;
13055     }
13056
13057     // [HGM] PV info: routine tests if comment empty
13058     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13059         DisplayComment(currentMove - 1, commentList[currentMove]);
13060     }
13061     if (!matchMode && appData.timeDelay != 0)
13062       DrawPosition(FALSE, boards[currentMove]);
13063
13064     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13065       programStats.ok_to_send = 1;
13066     }
13067
13068     /* if the first token after the PGN tags is a move
13069      * and not move number 1, retrieve it from the parser
13070      */
13071     if (cm != MoveNumberOne)
13072         LoadGameOneMove(cm);
13073
13074     /* load the remaining moves from the file */
13075     while (LoadGameOneMove(EndOfFile)) {
13076       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13077       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13078     }
13079
13080     /* rewind to the start of the game */
13081     currentMove = backwardMostMove;
13082
13083     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13084
13085     if (oldGameMode == AnalyzeFile) {
13086       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13087       AnalyzeFileEvent();
13088     } else
13089     if (oldGameMode == AnalyzeMode) {
13090       AnalyzeFileEvent();
13091     }
13092
13093     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13094         long int w, b; // [HGM] adjourn: restore saved clock times
13095         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13096         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13097             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13098             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13099         }
13100     }
13101
13102     if(creatingBook) return TRUE;
13103     if (!matchMode && pos > 0) {
13104         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13105     } else
13106     if (matchMode || appData.timeDelay == 0) {
13107       ToEndEvent();
13108     } else if (appData.timeDelay > 0) {
13109       AutoPlayGameLoop();
13110     }
13111
13112     if (appData.debugMode)
13113         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13114
13115     loadFlag = 0; /* [HGM] true game starts */
13116     return TRUE;
13117 }
13118
13119 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13120 int
13121 ReloadPosition (int offset)
13122 {
13123     int positionNumber = lastLoadPositionNumber + offset;
13124     if (lastLoadPositionFP == NULL) {
13125         DisplayError(_("No position has been loaded yet"), 0);
13126         return FALSE;
13127     }
13128     if (positionNumber <= 0) {
13129         DisplayError(_("Can't back up any further"), 0);
13130         return FALSE;
13131     }
13132     return LoadPosition(lastLoadPositionFP, positionNumber,
13133                         lastLoadPositionTitle);
13134 }
13135
13136 /* Load the nth position from the given file */
13137 int
13138 LoadPositionFromFile (char *filename, int n, char *title)
13139 {
13140     FILE *f;
13141     char buf[MSG_SIZ];
13142
13143     if (strcmp(filename, "-") == 0) {
13144         return LoadPosition(stdin, n, "stdin");
13145     } else {
13146         f = fopen(filename, "rb");
13147         if (f == NULL) {
13148             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13149             DisplayError(buf, errno);
13150             return FALSE;
13151         } else {
13152             return LoadPosition(f, n, title);
13153         }
13154     }
13155 }
13156
13157 /* Load the nth position from the given open file, and close it */
13158 int
13159 LoadPosition (FILE *f, int positionNumber, char *title)
13160 {
13161     char *p, line[MSG_SIZ];
13162     Board initial_position;
13163     int i, j, fenMode, pn;
13164
13165     if (gameMode == Training )
13166         SetTrainingModeOff();
13167
13168     if (gameMode != BeginningOfGame) {
13169         Reset(FALSE, TRUE);
13170     }
13171     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13172         fclose(lastLoadPositionFP);
13173     }
13174     if (positionNumber == 0) positionNumber = 1;
13175     lastLoadPositionFP = f;
13176     lastLoadPositionNumber = positionNumber;
13177     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13178     if (first.pr == NoProc && !appData.noChessProgram) {
13179       StartChessProgram(&first);
13180       InitChessProgram(&first, FALSE);
13181     }
13182     pn = positionNumber;
13183     if (positionNumber < 0) {
13184         /* Negative position number means to seek to that byte offset */
13185         if (fseek(f, -positionNumber, 0) == -1) {
13186             DisplayError(_("Can't seek on position file"), 0);
13187             return FALSE;
13188         };
13189         pn = 1;
13190     } else {
13191         if (fseek(f, 0, 0) == -1) {
13192             if (f == lastLoadPositionFP ?
13193                 positionNumber == lastLoadPositionNumber + 1 :
13194                 positionNumber == 1) {
13195                 pn = 1;
13196             } else {
13197                 DisplayError(_("Can't seek on position file"), 0);
13198                 return FALSE;
13199             }
13200         }
13201     }
13202     /* See if this file is FEN or old-style xboard */
13203     if (fgets(line, MSG_SIZ, f) == NULL) {
13204         DisplayError(_("Position not found in file"), 0);
13205         return FALSE;
13206     }
13207     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13208     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13209
13210     if (pn >= 2) {
13211         if (fenMode || line[0] == '#') pn--;
13212         while (pn > 0) {
13213             /* skip positions before number pn */
13214             if (fgets(line, MSG_SIZ, f) == NULL) {
13215                 Reset(TRUE, TRUE);
13216                 DisplayError(_("Position not found in file"), 0);
13217                 return FALSE;
13218             }
13219             if (fenMode || line[0] == '#') pn--;
13220         }
13221     }
13222
13223     if (fenMode) {
13224         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13225             DisplayError(_("Bad FEN position in file"), 0);
13226             return FALSE;
13227         }
13228     } else {
13229         (void) fgets(line, MSG_SIZ, f);
13230         (void) fgets(line, MSG_SIZ, f);
13231
13232         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13233             (void) fgets(line, MSG_SIZ, f);
13234             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13235                 if (*p == ' ')
13236                   continue;
13237                 initial_position[i][j++] = CharToPiece(*p);
13238             }
13239         }
13240
13241         blackPlaysFirst = FALSE;
13242         if (!feof(f)) {
13243             (void) fgets(line, MSG_SIZ, f);
13244             if (strncmp(line, "black", strlen("black"))==0)
13245               blackPlaysFirst = TRUE;
13246         }
13247     }
13248     startedFromSetupPosition = TRUE;
13249
13250     CopyBoard(boards[0], initial_position);
13251     if (blackPlaysFirst) {
13252         currentMove = forwardMostMove = backwardMostMove = 1;
13253         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13254         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13255         CopyBoard(boards[1], initial_position);
13256         DisplayMessage("", _("Black to play"));
13257     } else {
13258         currentMove = forwardMostMove = backwardMostMove = 0;
13259         DisplayMessage("", _("White to play"));
13260     }
13261     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13262     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13263         SendToProgram("force\n", &first);
13264         SendBoard(&first, forwardMostMove);
13265     }
13266     if (appData.debugMode) {
13267 int i, j;
13268   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13269   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13270         fprintf(debugFP, "Load Position\n");
13271     }
13272
13273     if (positionNumber > 1) {
13274       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13275         DisplayTitle(line);
13276     } else {
13277         DisplayTitle(title);
13278     }
13279     gameMode = EditGame;
13280     ModeHighlight();
13281     ResetClocks();
13282     timeRemaining[0][1] = whiteTimeRemaining;
13283     timeRemaining[1][1] = blackTimeRemaining;
13284     DrawPosition(FALSE, boards[currentMove]);
13285
13286     return TRUE;
13287 }
13288
13289
13290 void
13291 CopyPlayerNameIntoFileName (char **dest, char *src)
13292 {
13293     while (*src != NULLCHAR && *src != ',') {
13294         if (*src == ' ') {
13295             *(*dest)++ = '_';
13296             src++;
13297         } else {
13298             *(*dest)++ = *src++;
13299         }
13300     }
13301 }
13302
13303 char *
13304 DefaultFileName (char *ext)
13305 {
13306     static char def[MSG_SIZ];
13307     char *p;
13308
13309     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13310         p = def;
13311         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13312         *p++ = '-';
13313         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13314         *p++ = '.';
13315         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13316     } else {
13317         def[0] = NULLCHAR;
13318     }
13319     return def;
13320 }
13321
13322 /* Save the current game to the given file */
13323 int
13324 SaveGameToFile (char *filename, int append)
13325 {
13326     FILE *f;
13327     char buf[MSG_SIZ];
13328     int result, i, t,tot=0;
13329
13330     if (strcmp(filename, "-") == 0) {
13331         return SaveGame(stdout, 0, NULL);
13332     } else {
13333         for(i=0; i<10; i++) { // upto 10 tries
13334              f = fopen(filename, append ? "a" : "w");
13335              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13336              if(f || errno != 13) break;
13337              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13338              tot += t;
13339         }
13340         if (f == NULL) {
13341             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13342             DisplayError(buf, errno);
13343             return FALSE;
13344         } else {
13345             safeStrCpy(buf, lastMsg, MSG_SIZ);
13346             DisplayMessage(_("Waiting for access to save file"), "");
13347             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13348             DisplayMessage(_("Saving game"), "");
13349             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13350             result = SaveGame(f, 0, NULL);
13351             DisplayMessage(buf, "");
13352             return result;
13353         }
13354     }
13355 }
13356
13357 char *
13358 SavePart (char *str)
13359 {
13360     static char buf[MSG_SIZ];
13361     char *p;
13362
13363     p = strchr(str, ' ');
13364     if (p == NULL) return str;
13365     strncpy(buf, str, p - str);
13366     buf[p - str] = NULLCHAR;
13367     return buf;
13368 }
13369
13370 #define PGN_MAX_LINE 75
13371
13372 #define PGN_SIDE_WHITE  0
13373 #define PGN_SIDE_BLACK  1
13374
13375 static int
13376 FindFirstMoveOutOfBook (int side)
13377 {
13378     int result = -1;
13379
13380     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13381         int index = backwardMostMove;
13382         int has_book_hit = 0;
13383
13384         if( (index % 2) != side ) {
13385             index++;
13386         }
13387
13388         while( index < forwardMostMove ) {
13389             /* Check to see if engine is in book */
13390             int depth = pvInfoList[index].depth;
13391             int score = pvInfoList[index].score;
13392             int in_book = 0;
13393
13394             if( depth <= 2 ) {
13395                 in_book = 1;
13396             }
13397             else if( score == 0 && depth == 63 ) {
13398                 in_book = 1; /* Zappa */
13399             }
13400             else if( score == 2 && depth == 99 ) {
13401                 in_book = 1; /* Abrok */
13402             }
13403
13404             has_book_hit += in_book;
13405
13406             if( ! in_book ) {
13407                 result = index;
13408
13409                 break;
13410             }
13411
13412             index += 2;
13413         }
13414     }
13415
13416     return result;
13417 }
13418
13419 void
13420 GetOutOfBookInfo (char * buf)
13421 {
13422     int oob[2];
13423     int i;
13424     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13425
13426     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13427     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13428
13429     *buf = '\0';
13430
13431     if( oob[0] >= 0 || oob[1] >= 0 ) {
13432         for( i=0; i<2; i++ ) {
13433             int idx = oob[i];
13434
13435             if( idx >= 0 ) {
13436                 if( i > 0 && oob[0] >= 0 ) {
13437                     strcat( buf, "   " );
13438                 }
13439
13440                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13441                 sprintf( buf+strlen(buf), "%s%.2f",
13442                     pvInfoList[idx].score >= 0 ? "+" : "",
13443                     pvInfoList[idx].score / 100.0 );
13444             }
13445         }
13446     }
13447 }
13448
13449 /* Save game in PGN style */
13450 static void
13451 SaveGamePGN2 (FILE *f)
13452 {
13453     int i, offset, linelen, newblock;
13454 //    char *movetext;
13455     char numtext[32];
13456     int movelen, numlen, blank;
13457     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13458
13459     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13460
13461     PrintPGNTags(f, &gameInfo);
13462
13463     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13464
13465     if (backwardMostMove > 0 || startedFromSetupPosition) {
13466         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13467         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13468         fprintf(f, "\n{--------------\n");
13469         PrintPosition(f, backwardMostMove);
13470         fprintf(f, "--------------}\n");
13471         free(fen);
13472     }
13473     else {
13474         /* [AS] Out of book annotation */
13475         if( appData.saveOutOfBookInfo ) {
13476             char buf[64];
13477
13478             GetOutOfBookInfo( buf );
13479
13480             if( buf[0] != '\0' ) {
13481                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13482             }
13483         }
13484
13485         fprintf(f, "\n");
13486     }
13487
13488     i = backwardMostMove;
13489     linelen = 0;
13490     newblock = TRUE;
13491
13492     while (i < forwardMostMove) {
13493         /* Print comments preceding this move */
13494         if (commentList[i] != NULL) {
13495             if (linelen > 0) fprintf(f, "\n");
13496             fprintf(f, "%s", commentList[i]);
13497             linelen = 0;
13498             newblock = TRUE;
13499         }
13500
13501         /* Format move number */
13502         if ((i % 2) == 0)
13503           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13504         else
13505           if (newblock)
13506             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13507           else
13508             numtext[0] = NULLCHAR;
13509
13510         numlen = strlen(numtext);
13511         newblock = FALSE;
13512
13513         /* Print move number */
13514         blank = linelen > 0 && numlen > 0;
13515         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13516             fprintf(f, "\n");
13517             linelen = 0;
13518             blank = 0;
13519         }
13520         if (blank) {
13521             fprintf(f, " ");
13522             linelen++;
13523         }
13524         fprintf(f, "%s", numtext);
13525         linelen += numlen;
13526
13527         /* Get move */
13528         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13529         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13530
13531         /* Print move */
13532         blank = linelen > 0 && movelen > 0;
13533         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13534             fprintf(f, "\n");
13535             linelen = 0;
13536             blank = 0;
13537         }
13538         if (blank) {
13539             fprintf(f, " ");
13540             linelen++;
13541         }
13542         fprintf(f, "%s", move_buffer);
13543         linelen += movelen;
13544
13545         /* [AS] Add PV info if present */
13546         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13547             /* [HGM] add time */
13548             char buf[MSG_SIZ]; int seconds;
13549
13550             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13551
13552             if( seconds <= 0)
13553               buf[0] = 0;
13554             else
13555               if( seconds < 30 )
13556                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13557               else
13558                 {
13559                   seconds = (seconds + 4)/10; // round to full seconds
13560                   if( seconds < 60 )
13561                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13562                   else
13563                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13564                 }
13565
13566             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13567                       pvInfoList[i].score >= 0 ? "+" : "",
13568                       pvInfoList[i].score / 100.0,
13569                       pvInfoList[i].depth,
13570                       buf );
13571
13572             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13573
13574             /* Print score/depth */
13575             blank = linelen > 0 && movelen > 0;
13576             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13577                 fprintf(f, "\n");
13578                 linelen = 0;
13579                 blank = 0;
13580             }
13581             if (blank) {
13582                 fprintf(f, " ");
13583                 linelen++;
13584             }
13585             fprintf(f, "%s", move_buffer);
13586             linelen += movelen;
13587         }
13588
13589         i++;
13590     }
13591
13592     /* Start a new line */
13593     if (linelen > 0) fprintf(f, "\n");
13594
13595     /* Print comments after last move */
13596     if (commentList[i] != NULL) {
13597         fprintf(f, "%s\n", commentList[i]);
13598     }
13599
13600     /* Print result */
13601     if (gameInfo.resultDetails != NULL &&
13602         gameInfo.resultDetails[0] != NULLCHAR) {
13603         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13604         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13605            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13606             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13607         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13608     } else {
13609         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13610     }
13611 }
13612
13613 /* Save game in PGN style and close the file */
13614 int
13615 SaveGamePGN (FILE *f)
13616 {
13617     SaveGamePGN2(f);
13618     fclose(f);
13619     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13620     return TRUE;
13621 }
13622
13623 /* Save game in old style and close the file */
13624 int
13625 SaveGameOldStyle (FILE *f)
13626 {
13627     int i, offset;
13628     time_t tm;
13629
13630     tm = time((time_t *) NULL);
13631
13632     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13633     PrintOpponents(f);
13634
13635     if (backwardMostMove > 0 || startedFromSetupPosition) {
13636         fprintf(f, "\n[--------------\n");
13637         PrintPosition(f, backwardMostMove);
13638         fprintf(f, "--------------]\n");
13639     } else {
13640         fprintf(f, "\n");
13641     }
13642
13643     i = backwardMostMove;
13644     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13645
13646     while (i < forwardMostMove) {
13647         if (commentList[i] != NULL) {
13648             fprintf(f, "[%s]\n", commentList[i]);
13649         }
13650
13651         if ((i % 2) == 1) {
13652             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13653             i++;
13654         } else {
13655             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13656             i++;
13657             if (commentList[i] != NULL) {
13658                 fprintf(f, "\n");
13659                 continue;
13660             }
13661             if (i >= forwardMostMove) {
13662                 fprintf(f, "\n");
13663                 break;
13664             }
13665             fprintf(f, "%s\n", parseList[i]);
13666             i++;
13667         }
13668     }
13669
13670     if (commentList[i] != NULL) {
13671         fprintf(f, "[%s]\n", commentList[i]);
13672     }
13673
13674     /* This isn't really the old style, but it's close enough */
13675     if (gameInfo.resultDetails != NULL &&
13676         gameInfo.resultDetails[0] != NULLCHAR) {
13677         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13678                 gameInfo.resultDetails);
13679     } else {
13680         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13681     }
13682
13683     fclose(f);
13684     return TRUE;
13685 }
13686
13687 /* Save the current game to open file f and close the file */
13688 int
13689 SaveGame (FILE *f, int dummy, char *dummy2)
13690 {
13691     if (gameMode == EditPosition) EditPositionDone(TRUE);
13692     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13693     if (appData.oldSaveStyle)
13694       return SaveGameOldStyle(f);
13695     else
13696       return SaveGamePGN(f);
13697 }
13698
13699 /* Save the current position to the given file */
13700 int
13701 SavePositionToFile (char *filename)
13702 {
13703     FILE *f;
13704     char buf[MSG_SIZ];
13705
13706     if (strcmp(filename, "-") == 0) {
13707         return SavePosition(stdout, 0, NULL);
13708     } else {
13709         f = fopen(filename, "a");
13710         if (f == NULL) {
13711             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13712             DisplayError(buf, errno);
13713             return FALSE;
13714         } else {
13715             safeStrCpy(buf, lastMsg, MSG_SIZ);
13716             DisplayMessage(_("Waiting for access to save file"), "");
13717             flock(fileno(f), LOCK_EX); // [HGM] lock
13718             DisplayMessage(_("Saving position"), "");
13719             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13720             SavePosition(f, 0, NULL);
13721             DisplayMessage(buf, "");
13722             return TRUE;
13723         }
13724     }
13725 }
13726
13727 /* Save the current position to the given open file and close the file */
13728 int
13729 SavePosition (FILE *f, int dummy, char *dummy2)
13730 {
13731     time_t tm;
13732     char *fen;
13733
13734     if (gameMode == EditPosition) EditPositionDone(TRUE);
13735     if (appData.oldSaveStyle) {
13736         tm = time((time_t *) NULL);
13737
13738         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13739         PrintOpponents(f);
13740         fprintf(f, "[--------------\n");
13741         PrintPosition(f, currentMove);
13742         fprintf(f, "--------------]\n");
13743     } else {
13744         fen = PositionToFEN(currentMove, NULL, 1);
13745         fprintf(f, "%s\n", fen);
13746         free(fen);
13747     }
13748     fclose(f);
13749     return TRUE;
13750 }
13751
13752 void
13753 ReloadCmailMsgEvent (int unregister)
13754 {
13755 #if !WIN32
13756     static char *inFilename = NULL;
13757     static char *outFilename;
13758     int i;
13759     struct stat inbuf, outbuf;
13760     int status;
13761
13762     /* Any registered moves are unregistered if unregister is set, */
13763     /* i.e. invoked by the signal handler */
13764     if (unregister) {
13765         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13766             cmailMoveRegistered[i] = FALSE;
13767             if (cmailCommentList[i] != NULL) {
13768                 free(cmailCommentList[i]);
13769                 cmailCommentList[i] = NULL;
13770             }
13771         }
13772         nCmailMovesRegistered = 0;
13773     }
13774
13775     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13776         cmailResult[i] = CMAIL_NOT_RESULT;
13777     }
13778     nCmailResults = 0;
13779
13780     if (inFilename == NULL) {
13781         /* Because the filenames are static they only get malloced once  */
13782         /* and they never get freed                                      */
13783         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13784         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13785
13786         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13787         sprintf(outFilename, "%s.out", appData.cmailGameName);
13788     }
13789
13790     status = stat(outFilename, &outbuf);
13791     if (status < 0) {
13792         cmailMailedMove = FALSE;
13793     } else {
13794         status = stat(inFilename, &inbuf);
13795         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13796     }
13797
13798     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13799        counts the games, notes how each one terminated, etc.
13800
13801        It would be nice to remove this kludge and instead gather all
13802        the information while building the game list.  (And to keep it
13803        in the game list nodes instead of having a bunch of fixed-size
13804        parallel arrays.)  Note this will require getting each game's
13805        termination from the PGN tags, as the game list builder does
13806        not process the game moves.  --mann
13807        */
13808     cmailMsgLoaded = TRUE;
13809     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13810
13811     /* Load first game in the file or popup game menu */
13812     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13813
13814 #endif /* !WIN32 */
13815     return;
13816 }
13817
13818 int
13819 RegisterMove ()
13820 {
13821     FILE *f;
13822     char string[MSG_SIZ];
13823
13824     if (   cmailMailedMove
13825         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13826         return TRUE;            /* Allow free viewing  */
13827     }
13828
13829     /* Unregister move to ensure that we don't leave RegisterMove        */
13830     /* with the move registered when the conditions for registering no   */
13831     /* longer hold                                                       */
13832     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13833         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13834         nCmailMovesRegistered --;
13835
13836         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13837           {
13838               free(cmailCommentList[lastLoadGameNumber - 1]);
13839               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13840           }
13841     }
13842
13843     if (cmailOldMove == -1) {
13844         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13845         return FALSE;
13846     }
13847
13848     if (currentMove > cmailOldMove + 1) {
13849         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13850         return FALSE;
13851     }
13852
13853     if (currentMove < cmailOldMove) {
13854         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13855         return FALSE;
13856     }
13857
13858     if (forwardMostMove > currentMove) {
13859         /* Silently truncate extra moves */
13860         TruncateGame();
13861     }
13862
13863     if (   (currentMove == cmailOldMove + 1)
13864         || (   (currentMove == cmailOldMove)
13865             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13866                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13867         if (gameInfo.result != GameUnfinished) {
13868             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13869         }
13870
13871         if (commentList[currentMove] != NULL) {
13872             cmailCommentList[lastLoadGameNumber - 1]
13873               = StrSave(commentList[currentMove]);
13874         }
13875         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13876
13877         if (appData.debugMode)
13878           fprintf(debugFP, "Saving %s for game %d\n",
13879                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13880
13881         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13882
13883         f = fopen(string, "w");
13884         if (appData.oldSaveStyle) {
13885             SaveGameOldStyle(f); /* also closes the file */
13886
13887             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13888             f = fopen(string, "w");
13889             SavePosition(f, 0, NULL); /* also closes the file */
13890         } else {
13891             fprintf(f, "{--------------\n");
13892             PrintPosition(f, currentMove);
13893             fprintf(f, "--------------}\n\n");
13894
13895             SaveGame(f, 0, NULL); /* also closes the file*/
13896         }
13897
13898         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13899         nCmailMovesRegistered ++;
13900     } else if (nCmailGames == 1) {
13901         DisplayError(_("You have not made a move yet"), 0);
13902         return FALSE;
13903     }
13904
13905     return TRUE;
13906 }
13907
13908 void
13909 MailMoveEvent ()
13910 {
13911 #if !WIN32
13912     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13913     FILE *commandOutput;
13914     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13915     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13916     int nBuffers;
13917     int i;
13918     int archived;
13919     char *arcDir;
13920
13921     if (! cmailMsgLoaded) {
13922         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13923         return;
13924     }
13925
13926     if (nCmailGames == nCmailResults) {
13927         DisplayError(_("No unfinished games"), 0);
13928         return;
13929     }
13930
13931 #if CMAIL_PROHIBIT_REMAIL
13932     if (cmailMailedMove) {
13933       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);
13934         DisplayError(msg, 0);
13935         return;
13936     }
13937 #endif
13938
13939     if (! (cmailMailedMove || RegisterMove())) return;
13940
13941     if (   cmailMailedMove
13942         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13943       snprintf(string, MSG_SIZ, partCommandString,
13944                appData.debugMode ? " -v" : "", appData.cmailGameName);
13945         commandOutput = popen(string, "r");
13946
13947         if (commandOutput == NULL) {
13948             DisplayError(_("Failed to invoke cmail"), 0);
13949         } else {
13950             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13951                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13952             }
13953             if (nBuffers > 1) {
13954                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13955                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13956                 nBytes = MSG_SIZ - 1;
13957             } else {
13958                 (void) memcpy(msg, buffer, nBytes);
13959             }
13960             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13961
13962             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13963                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13964
13965                 archived = TRUE;
13966                 for (i = 0; i < nCmailGames; i ++) {
13967                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13968                         archived = FALSE;
13969                     }
13970                 }
13971                 if (   archived
13972                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13973                         != NULL)) {
13974                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13975                            arcDir,
13976                            appData.cmailGameName,
13977                            gameInfo.date);
13978                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13979                     cmailMsgLoaded = FALSE;
13980                 }
13981             }
13982
13983             DisplayInformation(msg);
13984             pclose(commandOutput);
13985         }
13986     } else {
13987         if ((*cmailMsg) != '\0') {
13988             DisplayInformation(cmailMsg);
13989         }
13990     }
13991
13992     return;
13993 #endif /* !WIN32 */
13994 }
13995
13996 char *
13997 CmailMsg ()
13998 {
13999 #if WIN32
14000     return NULL;
14001 #else
14002     int  prependComma = 0;
14003     char number[5];
14004     char string[MSG_SIZ];       /* Space for game-list */
14005     int  i;
14006
14007     if (!cmailMsgLoaded) return "";
14008
14009     if (cmailMailedMove) {
14010       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14011     } else {
14012         /* Create a list of games left */
14013       snprintf(string, MSG_SIZ, "[");
14014         for (i = 0; i < nCmailGames; i ++) {
14015             if (! (   cmailMoveRegistered[i]
14016                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14017                 if (prependComma) {
14018                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14019                 } else {
14020                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14021                     prependComma = 1;
14022                 }
14023
14024                 strcat(string, number);
14025             }
14026         }
14027         strcat(string, "]");
14028
14029         if (nCmailMovesRegistered + nCmailResults == 0) {
14030             switch (nCmailGames) {
14031               case 1:
14032                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14033                 break;
14034
14035               case 2:
14036                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14037                 break;
14038
14039               default:
14040                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14041                          nCmailGames);
14042                 break;
14043             }
14044         } else {
14045             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14046               case 1:
14047                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14048                          string);
14049                 break;
14050
14051               case 0:
14052                 if (nCmailResults == nCmailGames) {
14053                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14054                 } else {
14055                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14056                 }
14057                 break;
14058
14059               default:
14060                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14061                          string);
14062             }
14063         }
14064     }
14065     return cmailMsg;
14066 #endif /* WIN32 */
14067 }
14068
14069 void
14070 ResetGameEvent ()
14071 {
14072     if (gameMode == Training)
14073       SetTrainingModeOff();
14074
14075     Reset(TRUE, TRUE);
14076     cmailMsgLoaded = FALSE;
14077     if (appData.icsActive) {
14078       SendToICS(ics_prefix);
14079       SendToICS("refresh\n");
14080     }
14081 }
14082
14083 void
14084 ExitEvent (int status)
14085 {
14086     exiting++;
14087     if (exiting > 2) {
14088       /* Give up on clean exit */
14089       exit(status);
14090     }
14091     if (exiting > 1) {
14092       /* Keep trying for clean exit */
14093       return;
14094     }
14095
14096     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14097     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14098
14099     if (telnetISR != NULL) {
14100       RemoveInputSource(telnetISR);
14101     }
14102     if (icsPR != NoProc) {
14103       DestroyChildProcess(icsPR, TRUE);
14104     }
14105
14106     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14107     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14108
14109     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14110     /* make sure this other one finishes before killing it!                  */
14111     if(endingGame) { int count = 0;
14112         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14113         while(endingGame && count++ < 10) DoSleep(1);
14114         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14115     }
14116
14117     /* Kill off chess programs */
14118     if (first.pr != NoProc) {
14119         ExitAnalyzeMode();
14120
14121         DoSleep( appData.delayBeforeQuit );
14122         SendToProgram("quit\n", &first);
14123         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14124     }
14125     if (second.pr != NoProc) {
14126         DoSleep( appData.delayBeforeQuit );
14127         SendToProgram("quit\n", &second);
14128         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14129     }
14130     if (first.isr != NULL) {
14131         RemoveInputSource(first.isr);
14132     }
14133     if (second.isr != NULL) {
14134         RemoveInputSource(second.isr);
14135     }
14136
14137     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14138     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14139
14140     ShutDownFrontEnd();
14141     exit(status);
14142 }
14143
14144 void
14145 PauseEngine (ChessProgramState *cps)
14146 {
14147     SendToProgram("pause\n", cps);
14148     cps->pause = 2;
14149 }
14150
14151 void
14152 UnPauseEngine (ChessProgramState *cps)
14153 {
14154     SendToProgram("resume\n", cps);
14155     cps->pause = 1;
14156 }
14157
14158 void
14159 PauseEvent ()
14160 {
14161     if (appData.debugMode)
14162         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14163     if (pausing) {
14164         pausing = FALSE;
14165         ModeHighlight();
14166         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14167             StartClocks();
14168             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14169                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14170                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14171             }
14172             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14173             HandleMachineMove(stashedInputMove, stalledEngine);
14174             stalledEngine = NULL;
14175             return;
14176         }
14177         if (gameMode == MachinePlaysWhite ||
14178             gameMode == TwoMachinesPlay   ||
14179             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14180             if(first.pause)  UnPauseEngine(&first);
14181             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14182             if(second.pause) UnPauseEngine(&second);
14183             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14184             StartClocks();
14185         } else {
14186             DisplayBothClocks();
14187         }
14188         if (gameMode == PlayFromGameFile) {
14189             if (appData.timeDelay >= 0)
14190                 AutoPlayGameLoop();
14191         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14192             Reset(FALSE, TRUE);
14193             SendToICS(ics_prefix);
14194             SendToICS("refresh\n");
14195         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14196             ForwardInner(forwardMostMove);
14197         }
14198         pauseExamInvalid = FALSE;
14199     } else {
14200         switch (gameMode) {
14201           default:
14202             return;
14203           case IcsExamining:
14204             pauseExamForwardMostMove = forwardMostMove;
14205             pauseExamInvalid = FALSE;
14206             /* fall through */
14207           case IcsObserving:
14208           case IcsPlayingWhite:
14209           case IcsPlayingBlack:
14210             pausing = TRUE;
14211             ModeHighlight();
14212             return;
14213           case PlayFromGameFile:
14214             (void) StopLoadGameTimer();
14215             pausing = TRUE;
14216             ModeHighlight();
14217             break;
14218           case BeginningOfGame:
14219             if (appData.icsActive) return;
14220             /* else fall through */
14221           case MachinePlaysWhite:
14222           case MachinePlaysBlack:
14223           case TwoMachinesPlay:
14224             if (forwardMostMove == 0)
14225               return;           /* don't pause if no one has moved */
14226             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14227                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14228                 if(onMove->pause) {           // thinking engine can be paused
14229                     PauseEngine(onMove);      // do it
14230                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14231                         PauseEngine(onMove->other);
14232                     else
14233                         SendToProgram("easy\n", onMove->other);
14234                     StopClocks();
14235                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14236             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14237                 if(first.pause) {
14238                     PauseEngine(&first);
14239                     StopClocks();
14240                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14241             } else { // human on move, pause pondering by either method
14242                 if(first.pause)
14243                     PauseEngine(&first);
14244                 else if(appData.ponderNextMove)
14245                     SendToProgram("easy\n", &first);
14246                 StopClocks();
14247             }
14248             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14249           case AnalyzeMode:
14250             pausing = TRUE;
14251             ModeHighlight();
14252             break;
14253         }
14254     }
14255 }
14256
14257 void
14258 EditCommentEvent ()
14259 {
14260     char title[MSG_SIZ];
14261
14262     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14263       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14264     } else {
14265       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14266                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14267                parseList[currentMove - 1]);
14268     }
14269
14270     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14271 }
14272
14273
14274 void
14275 EditTagsEvent ()
14276 {
14277     char *tags = PGNTags(&gameInfo);
14278     bookUp = FALSE;
14279     EditTagsPopUp(tags, NULL);
14280     free(tags);
14281 }
14282
14283 void
14284 ToggleSecond ()
14285 {
14286   if(second.analyzing) {
14287     SendToProgram("exit\n", &second);
14288     second.analyzing = FALSE;
14289   } else {
14290     if (second.pr == NoProc) StartChessProgram(&second);
14291     InitChessProgram(&second, FALSE);
14292     FeedMovesToProgram(&second, currentMove);
14293
14294     SendToProgram("analyze\n", &second);
14295     second.analyzing = TRUE;
14296   }
14297 }
14298
14299 /* Toggle ShowThinking */
14300 void
14301 ToggleShowThinking()
14302 {
14303   appData.showThinking = !appData.showThinking;
14304   ShowThinkingEvent();
14305 }
14306
14307 int
14308 AnalyzeModeEvent ()
14309 {
14310     char buf[MSG_SIZ];
14311
14312     if (!first.analysisSupport) {
14313       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14314       DisplayError(buf, 0);
14315       return 0;
14316     }
14317     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14318     if (appData.icsActive) {
14319         if (gameMode != IcsObserving) {
14320           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14321             DisplayError(buf, 0);
14322             /* secure check */
14323             if (appData.icsEngineAnalyze) {
14324                 if (appData.debugMode)
14325                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14326                 ExitAnalyzeMode();
14327                 ModeHighlight();
14328             }
14329             return 0;
14330         }
14331         /* if enable, user wants to disable icsEngineAnalyze */
14332         if (appData.icsEngineAnalyze) {
14333                 ExitAnalyzeMode();
14334                 ModeHighlight();
14335                 return 0;
14336         }
14337         appData.icsEngineAnalyze = TRUE;
14338         if (appData.debugMode)
14339             fprintf(debugFP, "ICS engine analyze starting... \n");
14340     }
14341
14342     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14343     if (appData.noChessProgram || gameMode == AnalyzeMode)
14344       return 0;
14345
14346     if (gameMode != AnalyzeFile) {
14347         if (!appData.icsEngineAnalyze) {
14348                EditGameEvent();
14349                if (gameMode != EditGame) return 0;
14350         }
14351         if (!appData.showThinking) ToggleShowThinking();
14352         ResurrectChessProgram();
14353         SendToProgram("analyze\n", &first);
14354         first.analyzing = TRUE;
14355         /*first.maybeThinking = TRUE;*/
14356         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14357         EngineOutputPopUp();
14358     }
14359     if (!appData.icsEngineAnalyze) {
14360         gameMode = AnalyzeMode;
14361         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14362     }
14363     pausing = FALSE;
14364     ModeHighlight();
14365     SetGameInfo();
14366
14367     StartAnalysisClock();
14368     GetTimeMark(&lastNodeCountTime);
14369     lastNodeCount = 0;
14370     return 1;
14371 }
14372
14373 void
14374 AnalyzeFileEvent ()
14375 {
14376     if (appData.noChessProgram || gameMode == AnalyzeFile)
14377       return;
14378
14379     if (!first.analysisSupport) {
14380       char buf[MSG_SIZ];
14381       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14382       DisplayError(buf, 0);
14383       return;
14384     }
14385
14386     if (gameMode != AnalyzeMode) {
14387         keepInfo = 1; // mere annotating should not alter PGN tags
14388         EditGameEvent();
14389         keepInfo = 0;
14390         if (gameMode != EditGame) return;
14391         if (!appData.showThinking) ToggleShowThinking();
14392         ResurrectChessProgram();
14393         SendToProgram("analyze\n", &first);
14394         first.analyzing = TRUE;
14395         /*first.maybeThinking = TRUE;*/
14396         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14397         EngineOutputPopUp();
14398     }
14399     gameMode = AnalyzeFile;
14400     pausing = FALSE;
14401     ModeHighlight();
14402
14403     StartAnalysisClock();
14404     GetTimeMark(&lastNodeCountTime);
14405     lastNodeCount = 0;
14406     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14407     AnalysisPeriodicEvent(1);
14408 }
14409
14410 void
14411 MachineWhiteEvent ()
14412 {
14413     char buf[MSG_SIZ];
14414     char *bookHit = NULL;
14415
14416     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14417       return;
14418
14419
14420     if (gameMode == PlayFromGameFile ||
14421         gameMode == TwoMachinesPlay  ||
14422         gameMode == Training         ||
14423         gameMode == AnalyzeMode      ||
14424         gameMode == EndOfGame)
14425         EditGameEvent();
14426
14427     if (gameMode == EditPosition)
14428         EditPositionDone(TRUE);
14429
14430     if (!WhiteOnMove(currentMove)) {
14431         DisplayError(_("It is not White's turn"), 0);
14432         return;
14433     }
14434
14435     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14436       ExitAnalyzeMode();
14437
14438     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14439         gameMode == AnalyzeFile)
14440         TruncateGame();
14441
14442     ResurrectChessProgram();    /* in case it isn't running */
14443     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14444         gameMode = MachinePlaysWhite;
14445         ResetClocks();
14446     } else
14447     gameMode = MachinePlaysWhite;
14448     pausing = FALSE;
14449     ModeHighlight();
14450     SetGameInfo();
14451     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14452     DisplayTitle(buf);
14453     if (first.sendName) {
14454       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14455       SendToProgram(buf, &first);
14456     }
14457     if (first.sendTime) {
14458       if (first.useColors) {
14459         SendToProgram("black\n", &first); /*gnu kludge*/
14460       }
14461       SendTimeRemaining(&first, TRUE);
14462     }
14463     if (first.useColors) {
14464       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14465     }
14466     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14467     SetMachineThinkingEnables();
14468     first.maybeThinking = TRUE;
14469     StartClocks();
14470     firstMove = FALSE;
14471
14472     if (appData.autoFlipView && !flipView) {
14473       flipView = !flipView;
14474       DrawPosition(FALSE, NULL);
14475       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14476     }
14477
14478     if(bookHit) { // [HGM] book: simulate book reply
14479         static char bookMove[MSG_SIZ]; // a bit generous?
14480
14481         programStats.nodes = programStats.depth = programStats.time =
14482         programStats.score = programStats.got_only_move = 0;
14483         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14484
14485         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14486         strcat(bookMove, bookHit);
14487         HandleMachineMove(bookMove, &first);
14488     }
14489 }
14490
14491 void
14492 MachineBlackEvent ()
14493 {
14494   char buf[MSG_SIZ];
14495   char *bookHit = NULL;
14496
14497     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14498         return;
14499
14500
14501     if (gameMode == PlayFromGameFile ||
14502         gameMode == TwoMachinesPlay  ||
14503         gameMode == Training         ||
14504         gameMode == AnalyzeMode      ||
14505         gameMode == EndOfGame)
14506         EditGameEvent();
14507
14508     if (gameMode == EditPosition)
14509         EditPositionDone(TRUE);
14510
14511     if (WhiteOnMove(currentMove)) {
14512         DisplayError(_("It is not Black's turn"), 0);
14513         return;
14514     }
14515
14516     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14517       ExitAnalyzeMode();
14518
14519     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14520         gameMode == AnalyzeFile)
14521         TruncateGame();
14522
14523     ResurrectChessProgram();    /* in case it isn't running */
14524     gameMode = MachinePlaysBlack;
14525     pausing = FALSE;
14526     ModeHighlight();
14527     SetGameInfo();
14528     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14529     DisplayTitle(buf);
14530     if (first.sendName) {
14531       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14532       SendToProgram(buf, &first);
14533     }
14534     if (first.sendTime) {
14535       if (first.useColors) {
14536         SendToProgram("white\n", &first); /*gnu kludge*/
14537       }
14538       SendTimeRemaining(&first, FALSE);
14539     }
14540     if (first.useColors) {
14541       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14542     }
14543     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14544     SetMachineThinkingEnables();
14545     first.maybeThinking = TRUE;
14546     StartClocks();
14547
14548     if (appData.autoFlipView && flipView) {
14549       flipView = !flipView;
14550       DrawPosition(FALSE, NULL);
14551       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14552     }
14553     if(bookHit) { // [HGM] book: simulate book reply
14554         static char bookMove[MSG_SIZ]; // a bit generous?
14555
14556         programStats.nodes = programStats.depth = programStats.time =
14557         programStats.score = programStats.got_only_move = 0;
14558         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14559
14560         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14561         strcat(bookMove, bookHit);
14562         HandleMachineMove(bookMove, &first);
14563     }
14564 }
14565
14566
14567 void
14568 DisplayTwoMachinesTitle ()
14569 {
14570     char buf[MSG_SIZ];
14571     if (appData.matchGames > 0) {
14572         if(appData.tourneyFile[0]) {
14573           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14574                    gameInfo.white, _("vs."), gameInfo.black,
14575                    nextGame+1, appData.matchGames+1,
14576                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14577         } else
14578         if (first.twoMachinesColor[0] == 'w') {
14579           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14580                    gameInfo.white, _("vs."),  gameInfo.black,
14581                    first.matchWins, second.matchWins,
14582                    matchGame - 1 - (first.matchWins + second.matchWins));
14583         } else {
14584           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14585                    gameInfo.white, _("vs."), gameInfo.black,
14586                    second.matchWins, first.matchWins,
14587                    matchGame - 1 - (first.matchWins + second.matchWins));
14588         }
14589     } else {
14590       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14591     }
14592     DisplayTitle(buf);
14593 }
14594
14595 void
14596 SettingsMenuIfReady ()
14597 {
14598   if (second.lastPing != second.lastPong) {
14599     DisplayMessage("", _("Waiting for second chess program"));
14600     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14601     return;
14602   }
14603   ThawUI();
14604   DisplayMessage("", "");
14605   SettingsPopUp(&second);
14606 }
14607
14608 int
14609 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14610 {
14611     char buf[MSG_SIZ];
14612     if (cps->pr == NoProc) {
14613         StartChessProgram(cps);
14614         if (cps->protocolVersion == 1) {
14615           retry();
14616           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14617         } else {
14618           /* kludge: allow timeout for initial "feature" command */
14619           if(retry != TwoMachinesEventIfReady) FreezeUI();
14620           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14621           DisplayMessage("", buf);
14622           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14623         }
14624         return 1;
14625     }
14626     return 0;
14627 }
14628
14629 void
14630 TwoMachinesEvent P((void))
14631 {
14632     int i;
14633     char buf[MSG_SIZ];
14634     ChessProgramState *onmove;
14635     char *bookHit = NULL;
14636     static int stalling = 0;
14637     TimeMark now;
14638     long wait;
14639
14640     if (appData.noChessProgram) return;
14641
14642     switch (gameMode) {
14643       case TwoMachinesPlay:
14644         return;
14645       case MachinePlaysWhite:
14646       case MachinePlaysBlack:
14647         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14648             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14649             return;
14650         }
14651         /* fall through */
14652       case BeginningOfGame:
14653       case PlayFromGameFile:
14654       case EndOfGame:
14655         EditGameEvent();
14656         if (gameMode != EditGame) return;
14657         break;
14658       case EditPosition:
14659         EditPositionDone(TRUE);
14660         break;
14661       case AnalyzeMode:
14662       case AnalyzeFile:
14663         ExitAnalyzeMode();
14664         break;
14665       case EditGame:
14666       default:
14667         break;
14668     }
14669
14670 //    forwardMostMove = currentMove;
14671     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14672     startingEngine = TRUE;
14673
14674     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14675
14676     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14677     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14678       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14679       return;
14680     }
14681     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14682
14683     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14684                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14685         startingEngine = matchMode = FALSE;
14686         DisplayError("second engine does not play this", 0);
14687         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14688         EditGameEvent(); // switch back to EditGame mode
14689         return;
14690     }
14691
14692     if(!stalling) {
14693       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14694       SendToProgram("force\n", &second);
14695       stalling = 1;
14696       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14697       return;
14698     }
14699     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14700     if(appData.matchPause>10000 || appData.matchPause<10)
14701                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14702     wait = SubtractTimeMarks(&now, &pauseStart);
14703     if(wait < appData.matchPause) {
14704         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14705         return;
14706     }
14707     // we are now committed to starting the game
14708     stalling = 0;
14709     DisplayMessage("", "");
14710     if (startedFromSetupPosition) {
14711         SendBoard(&second, backwardMostMove);
14712     if (appData.debugMode) {
14713         fprintf(debugFP, "Two Machines\n");
14714     }
14715     }
14716     for (i = backwardMostMove; i < forwardMostMove; i++) {
14717         SendMoveToProgram(i, &second);
14718     }
14719
14720     gameMode = TwoMachinesPlay;
14721     pausing = startingEngine = FALSE;
14722     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14723     SetGameInfo();
14724     DisplayTwoMachinesTitle();
14725     firstMove = TRUE;
14726     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14727         onmove = &first;
14728     } else {
14729         onmove = &second;
14730     }
14731     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14732     SendToProgram(first.computerString, &first);
14733     if (first.sendName) {
14734       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14735       SendToProgram(buf, &first);
14736     }
14737     SendToProgram(second.computerString, &second);
14738     if (second.sendName) {
14739       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14740       SendToProgram(buf, &second);
14741     }
14742
14743     ResetClocks();
14744     if (!first.sendTime || !second.sendTime) {
14745         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14746         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14747     }
14748     if (onmove->sendTime) {
14749       if (onmove->useColors) {
14750         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14751       }
14752       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14753     }
14754     if (onmove->useColors) {
14755       SendToProgram(onmove->twoMachinesColor, onmove);
14756     }
14757     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14758 //    SendToProgram("go\n", onmove);
14759     onmove->maybeThinking = TRUE;
14760     SetMachineThinkingEnables();
14761
14762     StartClocks();
14763
14764     if(bookHit) { // [HGM] book: simulate book reply
14765         static char bookMove[MSG_SIZ]; // a bit generous?
14766
14767         programStats.nodes = programStats.depth = programStats.time =
14768         programStats.score = programStats.got_only_move = 0;
14769         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14770
14771         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14772         strcat(bookMove, bookHit);
14773         savedMessage = bookMove; // args for deferred call
14774         savedState = onmove;
14775         ScheduleDelayedEvent(DeferredBookMove, 1);
14776     }
14777 }
14778
14779 void
14780 TrainingEvent ()
14781 {
14782     if (gameMode == Training) {
14783       SetTrainingModeOff();
14784       gameMode = PlayFromGameFile;
14785       DisplayMessage("", _("Training mode off"));
14786     } else {
14787       gameMode = Training;
14788       animateTraining = appData.animate;
14789
14790       /* make sure we are not already at the end of the game */
14791       if (currentMove < forwardMostMove) {
14792         SetTrainingModeOn();
14793         DisplayMessage("", _("Training mode on"));
14794       } else {
14795         gameMode = PlayFromGameFile;
14796         DisplayError(_("Already at end of game"), 0);
14797       }
14798     }
14799     ModeHighlight();
14800 }
14801
14802 void
14803 IcsClientEvent ()
14804 {
14805     if (!appData.icsActive) return;
14806     switch (gameMode) {
14807       case IcsPlayingWhite:
14808       case IcsPlayingBlack:
14809       case IcsObserving:
14810       case IcsIdle:
14811       case BeginningOfGame:
14812       case IcsExamining:
14813         return;
14814
14815       case EditGame:
14816         break;
14817
14818       case EditPosition:
14819         EditPositionDone(TRUE);
14820         break;
14821
14822       case AnalyzeMode:
14823       case AnalyzeFile:
14824         ExitAnalyzeMode();
14825         break;
14826
14827       default:
14828         EditGameEvent();
14829         break;
14830     }
14831
14832     gameMode = IcsIdle;
14833     ModeHighlight();
14834     return;
14835 }
14836
14837 void
14838 EditGameEvent ()
14839 {
14840     int i;
14841
14842     switch (gameMode) {
14843       case Training:
14844         SetTrainingModeOff();
14845         break;
14846       case MachinePlaysWhite:
14847       case MachinePlaysBlack:
14848       case BeginningOfGame:
14849         SendToProgram("force\n", &first);
14850         SetUserThinkingEnables();
14851         break;
14852       case PlayFromGameFile:
14853         (void) StopLoadGameTimer();
14854         if (gameFileFP != NULL) {
14855             gameFileFP = NULL;
14856         }
14857         break;
14858       case EditPosition:
14859         EditPositionDone(TRUE);
14860         break;
14861       case AnalyzeMode:
14862       case AnalyzeFile:
14863         ExitAnalyzeMode();
14864         SendToProgram("force\n", &first);
14865         break;
14866       case TwoMachinesPlay:
14867         GameEnds(EndOfFile, NULL, GE_PLAYER);
14868         ResurrectChessProgram();
14869         SetUserThinkingEnables();
14870         break;
14871       case EndOfGame:
14872         ResurrectChessProgram();
14873         break;
14874       case IcsPlayingBlack:
14875       case IcsPlayingWhite:
14876         DisplayError(_("Warning: You are still playing a game"), 0);
14877         break;
14878       case IcsObserving:
14879         DisplayError(_("Warning: You are still observing a game"), 0);
14880         break;
14881       case IcsExamining:
14882         DisplayError(_("Warning: You are still examining a game"), 0);
14883         break;
14884       case IcsIdle:
14885         break;
14886       case EditGame:
14887       default:
14888         return;
14889     }
14890
14891     pausing = FALSE;
14892     StopClocks();
14893     first.offeredDraw = second.offeredDraw = 0;
14894
14895     if (gameMode == PlayFromGameFile) {
14896         whiteTimeRemaining = timeRemaining[0][currentMove];
14897         blackTimeRemaining = timeRemaining[1][currentMove];
14898         DisplayTitle("");
14899     }
14900
14901     if (gameMode == MachinePlaysWhite ||
14902         gameMode == MachinePlaysBlack ||
14903         gameMode == TwoMachinesPlay ||
14904         gameMode == EndOfGame) {
14905         i = forwardMostMove;
14906         while (i > currentMove) {
14907             SendToProgram("undo\n", &first);
14908             i--;
14909         }
14910         if(!adjustedClock) {
14911         whiteTimeRemaining = timeRemaining[0][currentMove];
14912         blackTimeRemaining = timeRemaining[1][currentMove];
14913         DisplayBothClocks();
14914         }
14915         if (whiteFlag || blackFlag) {
14916             whiteFlag = blackFlag = 0;
14917         }
14918         DisplayTitle("");
14919     }
14920
14921     gameMode = EditGame;
14922     ModeHighlight();
14923     SetGameInfo();
14924 }
14925
14926
14927 void
14928 EditPositionEvent ()
14929 {
14930     if (gameMode == EditPosition) {
14931         EditGameEvent();
14932         return;
14933     }
14934
14935     EditGameEvent();
14936     if (gameMode != EditGame) return;
14937
14938     gameMode = EditPosition;
14939     ModeHighlight();
14940     SetGameInfo();
14941     if (currentMove > 0)
14942       CopyBoard(boards[0], boards[currentMove]);
14943
14944     blackPlaysFirst = !WhiteOnMove(currentMove);
14945     ResetClocks();
14946     currentMove = forwardMostMove = backwardMostMove = 0;
14947     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14948     DisplayMove(-1);
14949     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14950 }
14951
14952 void
14953 ExitAnalyzeMode ()
14954 {
14955     /* [DM] icsEngineAnalyze - possible call from other functions */
14956     if (appData.icsEngineAnalyze) {
14957         appData.icsEngineAnalyze = FALSE;
14958
14959         DisplayMessage("",_("Close ICS engine analyze..."));
14960     }
14961     if (first.analysisSupport && first.analyzing) {
14962       SendToBoth("exit\n");
14963       first.analyzing = second.analyzing = FALSE;
14964     }
14965     thinkOutput[0] = NULLCHAR;
14966 }
14967
14968 void
14969 EditPositionDone (Boolean fakeRights)
14970 {
14971     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14972
14973     startedFromSetupPosition = TRUE;
14974     InitChessProgram(&first, FALSE);
14975     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14976       boards[0][EP_STATUS] = EP_NONE;
14977       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14978       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14979         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14980         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14981       } else boards[0][CASTLING][2] = NoRights;
14982       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14983         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14984         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14985       } else boards[0][CASTLING][5] = NoRights;
14986       if(gameInfo.variant == VariantSChess) {
14987         int i;
14988         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14989           boards[0][VIRGIN][i] = 0;
14990           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14991           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14992         }
14993       }
14994     }
14995     SendToProgram("force\n", &first);
14996     if (blackPlaysFirst) {
14997         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14998         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14999         currentMove = forwardMostMove = backwardMostMove = 1;
15000         CopyBoard(boards[1], boards[0]);
15001     } else {
15002         currentMove = forwardMostMove = backwardMostMove = 0;
15003     }
15004     SendBoard(&first, forwardMostMove);
15005     if (appData.debugMode) {
15006         fprintf(debugFP, "EditPosDone\n");
15007     }
15008     DisplayTitle("");
15009     DisplayMessage("", "");
15010     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15011     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15012     gameMode = EditGame;
15013     ModeHighlight();
15014     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15015     ClearHighlights(); /* [AS] */
15016 }
15017
15018 /* Pause for `ms' milliseconds */
15019 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15020 void
15021 TimeDelay (long ms)
15022 {
15023     TimeMark m1, m2;
15024
15025     GetTimeMark(&m1);
15026     do {
15027         GetTimeMark(&m2);
15028     } while (SubtractTimeMarks(&m2, &m1) < ms);
15029 }
15030
15031 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15032 void
15033 SendMultiLineToICS (char *buf)
15034 {
15035     char temp[MSG_SIZ+1], *p;
15036     int len;
15037
15038     len = strlen(buf);
15039     if (len > MSG_SIZ)
15040       len = MSG_SIZ;
15041
15042     strncpy(temp, buf, len);
15043     temp[len] = 0;
15044
15045     p = temp;
15046     while (*p) {
15047         if (*p == '\n' || *p == '\r')
15048           *p = ' ';
15049         ++p;
15050     }
15051
15052     strcat(temp, "\n");
15053     SendToICS(temp);
15054     SendToPlayer(temp, strlen(temp));
15055 }
15056
15057 void
15058 SetWhiteToPlayEvent ()
15059 {
15060     if (gameMode == EditPosition) {
15061         blackPlaysFirst = FALSE;
15062         DisplayBothClocks();    /* works because currentMove is 0 */
15063     } else if (gameMode == IcsExamining) {
15064         SendToICS(ics_prefix);
15065         SendToICS("tomove white\n");
15066     }
15067 }
15068
15069 void
15070 SetBlackToPlayEvent ()
15071 {
15072     if (gameMode == EditPosition) {
15073         blackPlaysFirst = TRUE;
15074         currentMove = 1;        /* kludge */
15075         DisplayBothClocks();
15076         currentMove = 0;
15077     } else if (gameMode == IcsExamining) {
15078         SendToICS(ics_prefix);
15079         SendToICS("tomove black\n");
15080     }
15081 }
15082
15083 void
15084 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15085 {
15086     char buf[MSG_SIZ];
15087     ChessSquare piece = boards[0][y][x];
15088     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15089     static int lastVariant;
15090
15091     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15092
15093     switch (selection) {
15094       case ClearBoard:
15095         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15096         MarkTargetSquares(1);
15097         CopyBoard(currentBoard, boards[0]);
15098         CopyBoard(menuBoard, initialPosition);
15099         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15100             SendToICS(ics_prefix);
15101             SendToICS("bsetup clear\n");
15102         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15103             SendToICS(ics_prefix);
15104             SendToICS("clearboard\n");
15105         } else {
15106             int nonEmpty = 0;
15107             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15108                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15109                 for (y = 0; y < BOARD_HEIGHT; y++) {
15110                     if (gameMode == IcsExamining) {
15111                         if (boards[currentMove][y][x] != EmptySquare) {
15112                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15113                                     AAA + x, ONE + y);
15114                             SendToICS(buf);
15115                         }
15116                     } else {
15117                         if(boards[0][y][x] != p) nonEmpty++;
15118                         boards[0][y][x] = p;
15119                     }
15120                 }
15121             }
15122             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15123                 int r;
15124                 for(r = 0; r < BOARD_HEIGHT; r++) {
15125                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15126                     ChessSquare p = menuBoard[r][x];
15127                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15128                   }
15129                 }
15130                 DisplayMessage("Clicking clock again restores position", "");
15131                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15132                 if(!nonEmpty) { // asked to clear an empty board
15133                     CopyBoard(boards[0], menuBoard);
15134                 } else
15135                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15136                     CopyBoard(boards[0], initialPosition);
15137                 } else
15138                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15139                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15140                     CopyBoard(boards[0], erasedBoard);
15141                 } else
15142                     CopyBoard(erasedBoard, currentBoard);
15143
15144             }
15145         }
15146         if (gameMode == EditPosition) {
15147             DrawPosition(FALSE, boards[0]);
15148         }
15149         break;
15150
15151       case WhitePlay:
15152         SetWhiteToPlayEvent();
15153         break;
15154
15155       case BlackPlay:
15156         SetBlackToPlayEvent();
15157         break;
15158
15159       case EmptySquare:
15160         if (gameMode == IcsExamining) {
15161             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15162             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15163             SendToICS(buf);
15164         } else {
15165             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15166                 if(x == BOARD_LEFT-2) {
15167                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15168                     boards[0][y][1] = 0;
15169                 } else
15170                 if(x == BOARD_RGHT+1) {
15171                     if(y >= gameInfo.holdingsSize) break;
15172                     boards[0][y][BOARD_WIDTH-2] = 0;
15173                 } else break;
15174             }
15175             boards[0][y][x] = EmptySquare;
15176             DrawPosition(FALSE, boards[0]);
15177         }
15178         break;
15179
15180       case PromotePiece:
15181         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15182            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15183             selection = (ChessSquare) (PROMOTED piece);
15184         } else if(piece == EmptySquare) selection = WhiteSilver;
15185         else selection = (ChessSquare)((int)piece - 1);
15186         goto defaultlabel;
15187
15188       case DemotePiece:
15189         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15190            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15191             selection = (ChessSquare) (DEMOTED piece);
15192         } else if(piece == EmptySquare) selection = BlackSilver;
15193         else selection = (ChessSquare)((int)piece + 1);
15194         goto defaultlabel;
15195
15196       case WhiteQueen:
15197       case BlackQueen:
15198         if(gameInfo.variant == VariantShatranj ||
15199            gameInfo.variant == VariantXiangqi  ||
15200            gameInfo.variant == VariantCourier  ||
15201            gameInfo.variant == VariantASEAN    ||
15202            gameInfo.variant == VariantMakruk     )
15203             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15204         goto defaultlabel;
15205
15206       case WhiteKing:
15207       case BlackKing:
15208         if(gameInfo.variant == VariantXiangqi)
15209             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15210         if(gameInfo.variant == VariantKnightmate)
15211             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15212       default:
15213         defaultlabel:
15214         if (gameMode == IcsExamining) {
15215             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15216             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15217                      PieceToChar(selection), AAA + x, ONE + y);
15218             SendToICS(buf);
15219         } else {
15220             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15221                 int n;
15222                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15223                     n = PieceToNumber(selection - BlackPawn);
15224                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15225                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15226                     boards[0][BOARD_HEIGHT-1-n][1]++;
15227                 } else
15228                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15229                     n = PieceToNumber(selection);
15230                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15231                     boards[0][n][BOARD_WIDTH-1] = selection;
15232                     boards[0][n][BOARD_WIDTH-2]++;
15233                 }
15234             } else
15235             boards[0][y][x] = selection;
15236             DrawPosition(TRUE, boards[0]);
15237             ClearHighlights();
15238             fromX = fromY = -1;
15239         }
15240         break;
15241     }
15242 }
15243
15244
15245 void
15246 DropMenuEvent (ChessSquare selection, int x, int y)
15247 {
15248     ChessMove moveType;
15249
15250     switch (gameMode) {
15251       case IcsPlayingWhite:
15252       case MachinePlaysBlack:
15253         if (!WhiteOnMove(currentMove)) {
15254             DisplayMoveError(_("It is Black's turn"));
15255             return;
15256         }
15257         moveType = WhiteDrop;
15258         break;
15259       case IcsPlayingBlack:
15260       case MachinePlaysWhite:
15261         if (WhiteOnMove(currentMove)) {
15262             DisplayMoveError(_("It is White's turn"));
15263             return;
15264         }
15265         moveType = BlackDrop;
15266         break;
15267       case EditGame:
15268         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15269         break;
15270       default:
15271         return;
15272     }
15273
15274     if (moveType == BlackDrop && selection < BlackPawn) {
15275       selection = (ChessSquare) ((int) selection
15276                                  + (int) BlackPawn - (int) WhitePawn);
15277     }
15278     if (boards[currentMove][y][x] != EmptySquare) {
15279         DisplayMoveError(_("That square is occupied"));
15280         return;
15281     }
15282
15283     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15284 }
15285
15286 void
15287 AcceptEvent ()
15288 {
15289     /* Accept a pending offer of any kind from opponent */
15290
15291     if (appData.icsActive) {
15292         SendToICS(ics_prefix);
15293         SendToICS("accept\n");
15294     } else if (cmailMsgLoaded) {
15295         if (currentMove == cmailOldMove &&
15296             commentList[cmailOldMove] != NULL &&
15297             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15298                    "Black offers a draw" : "White offers a draw")) {
15299             TruncateGame();
15300             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15301             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15302         } else {
15303             DisplayError(_("There is no pending offer on this move"), 0);
15304             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15305         }
15306     } else {
15307         /* Not used for offers from chess program */
15308     }
15309 }
15310
15311 void
15312 DeclineEvent ()
15313 {
15314     /* Decline a pending offer of any kind from opponent */
15315
15316     if (appData.icsActive) {
15317         SendToICS(ics_prefix);
15318         SendToICS("decline\n");
15319     } else if (cmailMsgLoaded) {
15320         if (currentMove == cmailOldMove &&
15321             commentList[cmailOldMove] != NULL &&
15322             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15323                    "Black offers a draw" : "White offers a draw")) {
15324 #ifdef NOTDEF
15325             AppendComment(cmailOldMove, "Draw declined", TRUE);
15326             DisplayComment(cmailOldMove - 1, "Draw declined");
15327 #endif /*NOTDEF*/
15328         } else {
15329             DisplayError(_("There is no pending offer on this move"), 0);
15330         }
15331     } else {
15332         /* Not used for offers from chess program */
15333     }
15334 }
15335
15336 void
15337 RematchEvent ()
15338 {
15339     /* Issue ICS rematch command */
15340     if (appData.icsActive) {
15341         SendToICS(ics_prefix);
15342         SendToICS("rematch\n");
15343     }
15344 }
15345
15346 void
15347 CallFlagEvent ()
15348 {
15349     /* Call your opponent's flag (claim a win on time) */
15350     if (appData.icsActive) {
15351         SendToICS(ics_prefix);
15352         SendToICS("flag\n");
15353     } else {
15354         switch (gameMode) {
15355           default:
15356             return;
15357           case MachinePlaysWhite:
15358             if (whiteFlag) {
15359                 if (blackFlag)
15360                   GameEnds(GameIsDrawn, "Both players ran out of time",
15361                            GE_PLAYER);
15362                 else
15363                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15364             } else {
15365                 DisplayError(_("Your opponent is not out of time"), 0);
15366             }
15367             break;
15368           case MachinePlaysBlack:
15369             if (blackFlag) {
15370                 if (whiteFlag)
15371                   GameEnds(GameIsDrawn, "Both players ran out of time",
15372                            GE_PLAYER);
15373                 else
15374                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15375             } else {
15376                 DisplayError(_("Your opponent is not out of time"), 0);
15377             }
15378             break;
15379         }
15380     }
15381 }
15382
15383 void
15384 ClockClick (int which)
15385 {       // [HGM] code moved to back-end from winboard.c
15386         if(which) { // black clock
15387           if (gameMode == EditPosition || gameMode == IcsExamining) {
15388             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15389             SetBlackToPlayEvent();
15390           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15391                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15392           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15393           } else if (shiftKey) {
15394             AdjustClock(which, -1);
15395           } else if (gameMode == IcsPlayingWhite ||
15396                      gameMode == MachinePlaysBlack) {
15397             CallFlagEvent();
15398           }
15399         } else { // white clock
15400           if (gameMode == EditPosition || gameMode == IcsExamining) {
15401             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15402             SetWhiteToPlayEvent();
15403           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15404                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15405           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15406           } else if (shiftKey) {
15407             AdjustClock(which, -1);
15408           } else if (gameMode == IcsPlayingBlack ||
15409                    gameMode == MachinePlaysWhite) {
15410             CallFlagEvent();
15411           }
15412         }
15413 }
15414
15415 void
15416 DrawEvent ()
15417 {
15418     /* Offer draw or accept pending draw offer from opponent */
15419
15420     if (appData.icsActive) {
15421         /* Note: tournament rules require draw offers to be
15422            made after you make your move but before you punch
15423            your clock.  Currently ICS doesn't let you do that;
15424            instead, you immediately punch your clock after making
15425            a move, but you can offer a draw at any time. */
15426
15427         SendToICS(ics_prefix);
15428         SendToICS("draw\n");
15429         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15430     } else if (cmailMsgLoaded) {
15431         if (currentMove == cmailOldMove &&
15432             commentList[cmailOldMove] != NULL &&
15433             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15434                    "Black offers a draw" : "White offers a draw")) {
15435             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15436             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15437         } else if (currentMove == cmailOldMove + 1) {
15438             char *offer = WhiteOnMove(cmailOldMove) ?
15439               "White offers a draw" : "Black offers a draw";
15440             AppendComment(currentMove, offer, TRUE);
15441             DisplayComment(currentMove - 1, offer);
15442             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15443         } else {
15444             DisplayError(_("You must make your move before offering a draw"), 0);
15445             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15446         }
15447     } else if (first.offeredDraw) {
15448         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15449     } else {
15450         if (first.sendDrawOffers) {
15451             SendToProgram("draw\n", &first);
15452             userOfferedDraw = TRUE;
15453         }
15454     }
15455 }
15456
15457 void
15458 AdjournEvent ()
15459 {
15460     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15461
15462     if (appData.icsActive) {
15463         SendToICS(ics_prefix);
15464         SendToICS("adjourn\n");
15465     } else {
15466         /* Currently GNU Chess doesn't offer or accept Adjourns */
15467     }
15468 }
15469
15470
15471 void
15472 AbortEvent ()
15473 {
15474     /* Offer Abort or accept pending Abort offer from opponent */
15475
15476     if (appData.icsActive) {
15477         SendToICS(ics_prefix);
15478         SendToICS("abort\n");
15479     } else {
15480         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15481     }
15482 }
15483
15484 void
15485 ResignEvent ()
15486 {
15487     /* Resign.  You can do this even if it's not your turn. */
15488
15489     if (appData.icsActive) {
15490         SendToICS(ics_prefix);
15491         SendToICS("resign\n");
15492     } else {
15493         switch (gameMode) {
15494           case MachinePlaysWhite:
15495             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15496             break;
15497           case MachinePlaysBlack:
15498             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15499             break;
15500           case EditGame:
15501             if (cmailMsgLoaded) {
15502                 TruncateGame();
15503                 if (WhiteOnMove(cmailOldMove)) {
15504                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15505                 } else {
15506                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15507                 }
15508                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15509             }
15510             break;
15511           default:
15512             break;
15513         }
15514     }
15515 }
15516
15517
15518 void
15519 StopObservingEvent ()
15520 {
15521     /* Stop observing current games */
15522     SendToICS(ics_prefix);
15523     SendToICS("unobserve\n");
15524 }
15525
15526 void
15527 StopExaminingEvent ()
15528 {
15529     /* Stop observing current game */
15530     SendToICS(ics_prefix);
15531     SendToICS("unexamine\n");
15532 }
15533
15534 void
15535 ForwardInner (int target)
15536 {
15537     int limit; int oldSeekGraphUp = seekGraphUp;
15538
15539     if (appData.debugMode)
15540         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15541                 target, currentMove, forwardMostMove);
15542
15543     if (gameMode == EditPosition)
15544       return;
15545
15546     seekGraphUp = FALSE;
15547     MarkTargetSquares(1);
15548     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15549
15550     if (gameMode == PlayFromGameFile && !pausing)
15551       PauseEvent();
15552
15553     if (gameMode == IcsExamining && pausing)
15554       limit = pauseExamForwardMostMove;
15555     else
15556       limit = forwardMostMove;
15557
15558     if (target > limit) target = limit;
15559
15560     if (target > 0 && moveList[target - 1][0]) {
15561         int fromX, fromY, toX, toY;
15562         toX = moveList[target - 1][2] - AAA;
15563         toY = moveList[target - 1][3] - ONE;
15564         if (moveList[target - 1][1] == '@') {
15565             if (appData.highlightLastMove) {
15566                 SetHighlights(-1, -1, toX, toY);
15567             }
15568         } else {
15569             int viaX = moveList[target - 1][5] - AAA;
15570             int viaY = moveList[target - 1][6] - ONE;
15571             fromX = moveList[target - 1][0] - AAA;
15572             fromY = moveList[target - 1][1] - ONE;
15573             if (target == currentMove + 1) {
15574                 if(moveList[target - 1][4] == ';') { // multi-leg
15575                     ChessSquare piece = boards[currentMove][viaY][viaX];
15576                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15577                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15578                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15579                     boards[currentMove][viaY][viaX] = piece;
15580                 } else
15581                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15582             }
15583             if (appData.highlightLastMove) {
15584                 SetHighlights(fromX, fromY, toX, toY);
15585             }
15586         }
15587     }
15588     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15589         gameMode == Training || gameMode == PlayFromGameFile ||
15590         gameMode == AnalyzeFile) {
15591         while (currentMove < target) {
15592             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15593             SendMoveToProgram(currentMove++, &first);
15594         }
15595     } else {
15596         currentMove = target;
15597     }
15598
15599     if (gameMode == EditGame || gameMode == EndOfGame) {
15600         whiteTimeRemaining = timeRemaining[0][currentMove];
15601         blackTimeRemaining = timeRemaining[1][currentMove];
15602     }
15603     DisplayBothClocks();
15604     DisplayMove(currentMove - 1);
15605     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15606     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15607     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15608         DisplayComment(currentMove - 1, commentList[currentMove]);
15609     }
15610     ClearMap(); // [HGM] exclude: invalidate map
15611 }
15612
15613
15614 void
15615 ForwardEvent ()
15616 {
15617     if (gameMode == IcsExamining && !pausing) {
15618         SendToICS(ics_prefix);
15619         SendToICS("forward\n");
15620     } else {
15621         ForwardInner(currentMove + 1);
15622     }
15623 }
15624
15625 void
15626 ToEndEvent ()
15627 {
15628     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15629         /* to optimze, we temporarily turn off analysis mode while we feed
15630          * the remaining moves to the engine. Otherwise we get analysis output
15631          * after each move.
15632          */
15633         if (first.analysisSupport) {
15634           SendToProgram("exit\nforce\n", &first);
15635           first.analyzing = FALSE;
15636         }
15637     }
15638
15639     if (gameMode == IcsExamining && !pausing) {
15640         SendToICS(ics_prefix);
15641         SendToICS("forward 999999\n");
15642     } else {
15643         ForwardInner(forwardMostMove);
15644     }
15645
15646     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15647         /* we have fed all the moves, so reactivate analysis mode */
15648         SendToProgram("analyze\n", &first);
15649         first.analyzing = TRUE;
15650         /*first.maybeThinking = TRUE;*/
15651         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15652     }
15653 }
15654
15655 void
15656 BackwardInner (int target)
15657 {
15658     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15659
15660     if (appData.debugMode)
15661         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15662                 target, currentMove, forwardMostMove);
15663
15664     if (gameMode == EditPosition) return;
15665     seekGraphUp = FALSE;
15666     MarkTargetSquares(1);
15667     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15668     if (currentMove <= backwardMostMove) {
15669         ClearHighlights();
15670         DrawPosition(full_redraw, boards[currentMove]);
15671         return;
15672     }
15673     if (gameMode == PlayFromGameFile && !pausing)
15674       PauseEvent();
15675
15676     if (moveList[target][0]) {
15677         int fromX, fromY, toX, toY;
15678         toX = moveList[target][2] - AAA;
15679         toY = moveList[target][3] - ONE;
15680         if (moveList[target][1] == '@') {
15681             if (appData.highlightLastMove) {
15682                 SetHighlights(-1, -1, toX, toY);
15683             }
15684         } else {
15685             fromX = moveList[target][0] - AAA;
15686             fromY = moveList[target][1] - ONE;
15687             if (target == currentMove - 1) {
15688                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15689             }
15690             if (appData.highlightLastMove) {
15691                 SetHighlights(fromX, fromY, toX, toY);
15692             }
15693         }
15694     }
15695     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15696         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15697         while (currentMove > target) {
15698             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15699                 // null move cannot be undone. Reload program with move history before it.
15700                 int i;
15701                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15702                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15703                 }
15704                 SendBoard(&first, i);
15705               if(second.analyzing) SendBoard(&second, i);
15706                 for(currentMove=i; currentMove<target; currentMove++) {
15707                     SendMoveToProgram(currentMove, &first);
15708                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15709                 }
15710                 break;
15711             }
15712             SendToBoth("undo\n");
15713             currentMove--;
15714         }
15715     } else {
15716         currentMove = target;
15717     }
15718
15719     if (gameMode == EditGame || gameMode == EndOfGame) {
15720         whiteTimeRemaining = timeRemaining[0][currentMove];
15721         blackTimeRemaining = timeRemaining[1][currentMove];
15722     }
15723     DisplayBothClocks();
15724     DisplayMove(currentMove - 1);
15725     DrawPosition(full_redraw, boards[currentMove]);
15726     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15727     // [HGM] PV info: routine tests if comment empty
15728     DisplayComment(currentMove - 1, commentList[currentMove]);
15729     ClearMap(); // [HGM] exclude: invalidate map
15730 }
15731
15732 void
15733 BackwardEvent ()
15734 {
15735     if (gameMode == IcsExamining && !pausing) {
15736         SendToICS(ics_prefix);
15737         SendToICS("backward\n");
15738     } else {
15739         BackwardInner(currentMove - 1);
15740     }
15741 }
15742
15743 void
15744 ToStartEvent ()
15745 {
15746     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15747         /* to optimize, we temporarily turn off analysis mode while we undo
15748          * all the moves. Otherwise we get analysis output after each undo.
15749          */
15750         if (first.analysisSupport) {
15751           SendToProgram("exit\nforce\n", &first);
15752           first.analyzing = FALSE;
15753         }
15754     }
15755
15756     if (gameMode == IcsExamining && !pausing) {
15757         SendToICS(ics_prefix);
15758         SendToICS("backward 999999\n");
15759     } else {
15760         BackwardInner(backwardMostMove);
15761     }
15762
15763     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15764         /* we have fed all the moves, so reactivate analysis mode */
15765         SendToProgram("analyze\n", &first);
15766         first.analyzing = TRUE;
15767         /*first.maybeThinking = TRUE;*/
15768         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15769     }
15770 }
15771
15772 void
15773 ToNrEvent (int to)
15774 {
15775   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15776   if (to >= forwardMostMove) to = forwardMostMove;
15777   if (to <= backwardMostMove) to = backwardMostMove;
15778   if (to < currentMove) {
15779     BackwardInner(to);
15780   } else {
15781     ForwardInner(to);
15782   }
15783 }
15784
15785 void
15786 RevertEvent (Boolean annotate)
15787 {
15788     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15789         return;
15790     }
15791     if (gameMode != IcsExamining) {
15792         DisplayError(_("You are not examining a game"), 0);
15793         return;
15794     }
15795     if (pausing) {
15796         DisplayError(_("You can't revert while pausing"), 0);
15797         return;
15798     }
15799     SendToICS(ics_prefix);
15800     SendToICS("revert\n");
15801 }
15802
15803 void
15804 RetractMoveEvent ()
15805 {
15806     switch (gameMode) {
15807       case MachinePlaysWhite:
15808       case MachinePlaysBlack:
15809         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15810             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15811             return;
15812         }
15813         if (forwardMostMove < 2) return;
15814         currentMove = forwardMostMove = forwardMostMove - 2;
15815         whiteTimeRemaining = timeRemaining[0][currentMove];
15816         blackTimeRemaining = timeRemaining[1][currentMove];
15817         DisplayBothClocks();
15818         DisplayMove(currentMove - 1);
15819         ClearHighlights();/*!! could figure this out*/
15820         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15821         SendToProgram("remove\n", &first);
15822         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15823         break;
15824
15825       case BeginningOfGame:
15826       default:
15827         break;
15828
15829       case IcsPlayingWhite:
15830       case IcsPlayingBlack:
15831         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15832             SendToICS(ics_prefix);
15833             SendToICS("takeback 2\n");
15834         } else {
15835             SendToICS(ics_prefix);
15836             SendToICS("takeback 1\n");
15837         }
15838         break;
15839     }
15840 }
15841
15842 void
15843 MoveNowEvent ()
15844 {
15845     ChessProgramState *cps;
15846
15847     switch (gameMode) {
15848       case MachinePlaysWhite:
15849         if (!WhiteOnMove(forwardMostMove)) {
15850             DisplayError(_("It is your turn"), 0);
15851             return;
15852         }
15853         cps = &first;
15854         break;
15855       case MachinePlaysBlack:
15856         if (WhiteOnMove(forwardMostMove)) {
15857             DisplayError(_("It is your turn"), 0);
15858             return;
15859         }
15860         cps = &first;
15861         break;
15862       case TwoMachinesPlay:
15863         if (WhiteOnMove(forwardMostMove) ==
15864             (first.twoMachinesColor[0] == 'w')) {
15865             cps = &first;
15866         } else {
15867             cps = &second;
15868         }
15869         break;
15870       case BeginningOfGame:
15871       default:
15872         return;
15873     }
15874     SendToProgram("?\n", cps);
15875 }
15876
15877 void
15878 TruncateGameEvent ()
15879 {
15880     EditGameEvent();
15881     if (gameMode != EditGame) return;
15882     TruncateGame();
15883 }
15884
15885 void
15886 TruncateGame ()
15887 {
15888     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15889     if (forwardMostMove > currentMove) {
15890         if (gameInfo.resultDetails != NULL) {
15891             free(gameInfo.resultDetails);
15892             gameInfo.resultDetails = NULL;
15893             gameInfo.result = GameUnfinished;
15894         }
15895         forwardMostMove = currentMove;
15896         HistorySet(parseList, backwardMostMove, forwardMostMove,
15897                    currentMove-1);
15898     }
15899 }
15900
15901 void
15902 HintEvent ()
15903 {
15904     if (appData.noChessProgram) return;
15905     switch (gameMode) {
15906       case MachinePlaysWhite:
15907         if (WhiteOnMove(forwardMostMove)) {
15908             DisplayError(_("Wait until your turn."), 0);
15909             return;
15910         }
15911         break;
15912       case BeginningOfGame:
15913       case MachinePlaysBlack:
15914         if (!WhiteOnMove(forwardMostMove)) {
15915             DisplayError(_("Wait until your turn."), 0);
15916             return;
15917         }
15918         break;
15919       default:
15920         DisplayError(_("No hint available"), 0);
15921         return;
15922     }
15923     SendToProgram("hint\n", &first);
15924     hintRequested = TRUE;
15925 }
15926
15927 int
15928 SaveSelected (FILE *g, int dummy, char *dummy2)
15929 {
15930     ListGame * lg = (ListGame *) gameList.head;
15931     int nItem, cnt=0;
15932     FILE *f;
15933
15934     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15935         DisplayError(_("Game list not loaded or empty"), 0);
15936         return 0;
15937     }
15938
15939     creatingBook = TRUE; // suppresses stuff during load game
15940
15941     /* Get list size */
15942     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15943         if(lg->position >= 0) { // selected?
15944             LoadGame(f, nItem, "", TRUE);
15945             SaveGamePGN2(g); // leaves g open
15946             cnt++; DoEvents();
15947         }
15948         lg = (ListGame *) lg->node.succ;
15949     }
15950
15951     fclose(g);
15952     creatingBook = FALSE;
15953
15954     return cnt;
15955 }
15956
15957 void
15958 CreateBookEvent ()
15959 {
15960     ListGame * lg = (ListGame *) gameList.head;
15961     FILE *f, *g;
15962     int nItem;
15963     static int secondTime = FALSE;
15964
15965     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15966         DisplayError(_("Game list not loaded or empty"), 0);
15967         return;
15968     }
15969
15970     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15971         fclose(g);
15972         secondTime++;
15973         DisplayNote(_("Book file exists! Try again for overwrite."));
15974         return;
15975     }
15976
15977     creatingBook = TRUE;
15978     secondTime = FALSE;
15979
15980     /* Get list size */
15981     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15982         if(lg->position >= 0) {
15983             LoadGame(f, nItem, "", TRUE);
15984             AddGameToBook(TRUE);
15985             DoEvents();
15986         }
15987         lg = (ListGame *) lg->node.succ;
15988     }
15989
15990     creatingBook = FALSE;
15991     FlushBook();
15992 }
15993
15994 void
15995 BookEvent ()
15996 {
15997     if (appData.noChessProgram) return;
15998     switch (gameMode) {
15999       case MachinePlaysWhite:
16000         if (WhiteOnMove(forwardMostMove)) {
16001             DisplayError(_("Wait until your turn."), 0);
16002             return;
16003         }
16004         break;
16005       case BeginningOfGame:
16006       case MachinePlaysBlack:
16007         if (!WhiteOnMove(forwardMostMove)) {
16008             DisplayError(_("Wait until your turn."), 0);
16009             return;
16010         }
16011         break;
16012       case EditPosition:
16013         EditPositionDone(TRUE);
16014         break;
16015       case TwoMachinesPlay:
16016         return;
16017       default:
16018         break;
16019     }
16020     SendToProgram("bk\n", &first);
16021     bookOutput[0] = NULLCHAR;
16022     bookRequested = TRUE;
16023 }
16024
16025 void
16026 AboutGameEvent ()
16027 {
16028     char *tags = PGNTags(&gameInfo);
16029     TagsPopUp(tags, CmailMsg());
16030     free(tags);
16031 }
16032
16033 /* end button procedures */
16034
16035 void
16036 PrintPosition (FILE *fp, int move)
16037 {
16038     int i, j;
16039
16040     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16041         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16042             char c = PieceToChar(boards[move][i][j]);
16043             fputc(c == 'x' ? '.' : c, fp);
16044             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16045         }
16046     }
16047     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16048       fprintf(fp, "white to play\n");
16049     else
16050       fprintf(fp, "black to play\n");
16051 }
16052
16053 void
16054 PrintOpponents (FILE *fp)
16055 {
16056     if (gameInfo.white != NULL) {
16057         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16058     } else {
16059         fprintf(fp, "\n");
16060     }
16061 }
16062
16063 /* Find last component of program's own name, using some heuristics */
16064 void
16065 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16066 {
16067     char *p, *q, c;
16068     int local = (strcmp(host, "localhost") == 0);
16069     while (!local && (p = strchr(prog, ';')) != NULL) {
16070         p++;
16071         while (*p == ' ') p++;
16072         prog = p;
16073     }
16074     if (*prog == '"' || *prog == '\'') {
16075         q = strchr(prog + 1, *prog);
16076     } else {
16077         q = strchr(prog, ' ');
16078     }
16079     if (q == NULL) q = prog + strlen(prog);
16080     p = q;
16081     while (p >= prog && *p != '/' && *p != '\\') p--;
16082     p++;
16083     if(p == prog && *p == '"') p++;
16084     c = *q; *q = 0;
16085     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16086     memcpy(buf, p, q - p);
16087     buf[q - p] = NULLCHAR;
16088     if (!local) {
16089         strcat(buf, "@");
16090         strcat(buf, host);
16091     }
16092 }
16093
16094 char *
16095 TimeControlTagValue ()
16096 {
16097     char buf[MSG_SIZ];
16098     if (!appData.clockMode) {
16099       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16100     } else if (movesPerSession > 0) {
16101       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16102     } else if (timeIncrement == 0) {
16103       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16104     } else {
16105       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16106     }
16107     return StrSave(buf);
16108 }
16109
16110 void
16111 SetGameInfo ()
16112 {
16113     /* This routine is used only for certain modes */
16114     VariantClass v = gameInfo.variant;
16115     ChessMove r = GameUnfinished;
16116     char *p = NULL;
16117
16118     if(keepInfo) return;
16119
16120     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16121         r = gameInfo.result;
16122         p = gameInfo.resultDetails;
16123         gameInfo.resultDetails = NULL;
16124     }
16125     ClearGameInfo(&gameInfo);
16126     gameInfo.variant = v;
16127
16128     switch (gameMode) {
16129       case MachinePlaysWhite:
16130         gameInfo.event = StrSave( appData.pgnEventHeader );
16131         gameInfo.site = StrSave(HostName());
16132         gameInfo.date = PGNDate();
16133         gameInfo.round = StrSave("-");
16134         gameInfo.white = StrSave(first.tidy);
16135         gameInfo.black = StrSave(UserName());
16136         gameInfo.timeControl = TimeControlTagValue();
16137         break;
16138
16139       case MachinePlaysBlack:
16140         gameInfo.event = StrSave( appData.pgnEventHeader );
16141         gameInfo.site = StrSave(HostName());
16142         gameInfo.date = PGNDate();
16143         gameInfo.round = StrSave("-");
16144         gameInfo.white = StrSave(UserName());
16145         gameInfo.black = StrSave(first.tidy);
16146         gameInfo.timeControl = TimeControlTagValue();
16147         break;
16148
16149       case TwoMachinesPlay:
16150         gameInfo.event = StrSave( appData.pgnEventHeader );
16151         gameInfo.site = StrSave(HostName());
16152         gameInfo.date = PGNDate();
16153         if (roundNr > 0) {
16154             char buf[MSG_SIZ];
16155             snprintf(buf, MSG_SIZ, "%d", roundNr);
16156             gameInfo.round = StrSave(buf);
16157         } else {
16158             gameInfo.round = StrSave("-");
16159         }
16160         if (first.twoMachinesColor[0] == 'w') {
16161             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16162             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16163         } else {
16164             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16165             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16166         }
16167         gameInfo.timeControl = TimeControlTagValue();
16168         break;
16169
16170       case EditGame:
16171         gameInfo.event = StrSave("Edited game");
16172         gameInfo.site = StrSave(HostName());
16173         gameInfo.date = PGNDate();
16174         gameInfo.round = StrSave("-");
16175         gameInfo.white = StrSave("-");
16176         gameInfo.black = StrSave("-");
16177         gameInfo.result = r;
16178         gameInfo.resultDetails = p;
16179         break;
16180
16181       case EditPosition:
16182         gameInfo.event = StrSave("Edited position");
16183         gameInfo.site = StrSave(HostName());
16184         gameInfo.date = PGNDate();
16185         gameInfo.round = StrSave("-");
16186         gameInfo.white = StrSave("-");
16187         gameInfo.black = StrSave("-");
16188         break;
16189
16190       case IcsPlayingWhite:
16191       case IcsPlayingBlack:
16192       case IcsObserving:
16193       case IcsExamining:
16194         break;
16195
16196       case PlayFromGameFile:
16197         gameInfo.event = StrSave("Game from non-PGN file");
16198         gameInfo.site = StrSave(HostName());
16199         gameInfo.date = PGNDate();
16200         gameInfo.round = StrSave("-");
16201         gameInfo.white = StrSave("?");
16202         gameInfo.black = StrSave("?");
16203         break;
16204
16205       default:
16206         break;
16207     }
16208 }
16209
16210 void
16211 ReplaceComment (int index, char *text)
16212 {
16213     int len;
16214     char *p;
16215     float score;
16216
16217     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16218        pvInfoList[index-1].depth == len &&
16219        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16220        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16221     while (*text == '\n') text++;
16222     len = strlen(text);
16223     while (len > 0 && text[len - 1] == '\n') len--;
16224
16225     if (commentList[index] != NULL)
16226       free(commentList[index]);
16227
16228     if (len == 0) {
16229         commentList[index] = NULL;
16230         return;
16231     }
16232   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16233       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16234       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16235     commentList[index] = (char *) malloc(len + 2);
16236     strncpy(commentList[index], text, len);
16237     commentList[index][len] = '\n';
16238     commentList[index][len + 1] = NULLCHAR;
16239   } else {
16240     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16241     char *p;
16242     commentList[index] = (char *) malloc(len + 7);
16243     safeStrCpy(commentList[index], "{\n", 3);
16244     safeStrCpy(commentList[index]+2, text, len+1);
16245     commentList[index][len+2] = NULLCHAR;
16246     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16247     strcat(commentList[index], "\n}\n");
16248   }
16249 }
16250
16251 void
16252 CrushCRs (char *text)
16253 {
16254   char *p = text;
16255   char *q = text;
16256   char ch;
16257
16258   do {
16259     ch = *p++;
16260     if (ch == '\r') continue;
16261     *q++ = ch;
16262   } while (ch != '\0');
16263 }
16264
16265 void
16266 AppendComment (int index, char *text, Boolean addBraces)
16267 /* addBraces  tells if we should add {} */
16268 {
16269     int oldlen, len;
16270     char *old;
16271
16272 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16273     if(addBraces == 3) addBraces = 0; else // force appending literally
16274     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16275
16276     CrushCRs(text);
16277     while (*text == '\n') text++;
16278     len = strlen(text);
16279     while (len > 0 && text[len - 1] == '\n') len--;
16280     text[len] = NULLCHAR;
16281
16282     if (len == 0) return;
16283
16284     if (commentList[index] != NULL) {
16285       Boolean addClosingBrace = addBraces;
16286         old = commentList[index];
16287         oldlen = strlen(old);
16288         while(commentList[index][oldlen-1] ==  '\n')
16289           commentList[index][--oldlen] = NULLCHAR;
16290         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16291         safeStrCpy(commentList[index], old, oldlen + len + 6);
16292         free(old);
16293         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16294         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16295           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16296           while (*text == '\n') { text++; len--; }
16297           commentList[index][--oldlen] = NULLCHAR;
16298       }
16299         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16300         else          strcat(commentList[index], "\n");
16301         strcat(commentList[index], text);
16302         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16303         else          strcat(commentList[index], "\n");
16304     } else {
16305         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16306         if(addBraces)
16307           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16308         else commentList[index][0] = NULLCHAR;
16309         strcat(commentList[index], text);
16310         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16311         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16312     }
16313 }
16314
16315 static char *
16316 FindStr (char * text, char * sub_text)
16317 {
16318     char * result = strstr( text, sub_text );
16319
16320     if( result != NULL ) {
16321         result += strlen( sub_text );
16322     }
16323
16324     return result;
16325 }
16326
16327 /* [AS] Try to extract PV info from PGN comment */
16328 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16329 char *
16330 GetInfoFromComment (int index, char * text)
16331 {
16332     char * sep = text, *p;
16333
16334     if( text != NULL && index > 0 ) {
16335         int score = 0;
16336         int depth = 0;
16337         int time = -1, sec = 0, deci;
16338         char * s_eval = FindStr( text, "[%eval " );
16339         char * s_emt = FindStr( text, "[%emt " );
16340 #if 0
16341         if( s_eval != NULL || s_emt != NULL ) {
16342 #else
16343         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16344 #endif
16345             /* New style */
16346             char delim;
16347
16348             if( s_eval != NULL ) {
16349                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16350                     return text;
16351                 }
16352
16353                 if( delim != ']' ) {
16354                     return text;
16355                 }
16356             }
16357
16358             if( s_emt != NULL ) {
16359             }
16360                 return text;
16361         }
16362         else {
16363             /* We expect something like: [+|-]nnn.nn/dd */
16364             int score_lo = 0;
16365
16366             if(*text != '{') return text; // [HGM] braces: must be normal comment
16367
16368             sep = strchr( text, '/' );
16369             if( sep == NULL || sep < (text+4) ) {
16370                 return text;
16371             }
16372
16373             p = text;
16374             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16375             if(p[1] == '(') { // comment starts with PV
16376                p = strchr(p, ')'); // locate end of PV
16377                if(p == NULL || sep < p+5) return text;
16378                // at this point we have something like "{(.*) +0.23/6 ..."
16379                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16380                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16381                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16382             }
16383             time = -1; sec = -1; deci = -1;
16384             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16385                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16386                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16387                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16388                 return text;
16389             }
16390
16391             if( score_lo < 0 || score_lo >= 100 ) {
16392                 return text;
16393             }
16394
16395             if(sec >= 0) time = 600*time + 10*sec; else
16396             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16397
16398             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16399
16400             /* [HGM] PV time: now locate end of PV info */
16401             while( *++sep >= '0' && *sep <= '9'); // strip depth
16402             if(time >= 0)
16403             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16404             if(sec >= 0)
16405             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16406             if(deci >= 0)
16407             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16408             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16409         }
16410
16411         if( depth <= 0 ) {
16412             return text;
16413         }
16414
16415         if( time < 0 ) {
16416             time = -1;
16417         }
16418
16419         pvInfoList[index-1].depth = depth;
16420         pvInfoList[index-1].score = score;
16421         pvInfoList[index-1].time  = 10*time; // centi-sec
16422         if(*sep == '}') *sep = 0; else *--sep = '{';
16423         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16424     }
16425     return sep;
16426 }
16427
16428 void
16429 SendToProgram (char *message, ChessProgramState *cps)
16430 {
16431     int count, outCount, error;
16432     char buf[MSG_SIZ];
16433
16434     if (cps->pr == NoProc) return;
16435     Attention(cps);
16436
16437     if (appData.debugMode) {
16438         TimeMark now;
16439         GetTimeMark(&now);
16440         fprintf(debugFP, "%ld >%-6s: %s",
16441                 SubtractTimeMarks(&now, &programStartTime),
16442                 cps->which, message);
16443         if(serverFP)
16444             fprintf(serverFP, "%ld >%-6s: %s",
16445                 SubtractTimeMarks(&now, &programStartTime),
16446                 cps->which, message), fflush(serverFP);
16447     }
16448
16449     count = strlen(message);
16450     outCount = OutputToProcess(cps->pr, message, count, &error);
16451     if (outCount < count && !exiting
16452                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16453       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16454       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16455         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16456             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16457                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16458                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16459                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16460             } else {
16461                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16462                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16463                 gameInfo.result = res;
16464             }
16465             gameInfo.resultDetails = StrSave(buf);
16466         }
16467         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16468         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16469     }
16470 }
16471
16472 void
16473 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16474 {
16475     char *end_str;
16476     char buf[MSG_SIZ];
16477     ChessProgramState *cps = (ChessProgramState *)closure;
16478
16479     if (isr != cps->isr) return; /* Killed intentionally */
16480     if (count <= 0) {
16481         if (count == 0) {
16482             RemoveInputSource(cps->isr);
16483             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16484                     _(cps->which), cps->program);
16485             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16486             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16487                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16488                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16489                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16490                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16491                 } else {
16492                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16493                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16494                     gameInfo.result = res;
16495                 }
16496                 gameInfo.resultDetails = StrSave(buf);
16497             }
16498             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16499             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16500         } else {
16501             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16502                     _(cps->which), cps->program);
16503             RemoveInputSource(cps->isr);
16504
16505             /* [AS] Program is misbehaving badly... kill it */
16506             if( count == -2 ) {
16507                 DestroyChildProcess( cps->pr, 9 );
16508                 cps->pr = NoProc;
16509             }
16510
16511             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16512         }
16513         return;
16514     }
16515
16516     if ((end_str = strchr(message, '\r')) != NULL)
16517       *end_str = NULLCHAR;
16518     if ((end_str = strchr(message, '\n')) != NULL)
16519       *end_str = NULLCHAR;
16520
16521     if (appData.debugMode) {
16522         TimeMark now; int print = 1;
16523         char *quote = ""; char c; int i;
16524
16525         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16526                 char start = message[0];
16527                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16528                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16529                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16530                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16531                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16532                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16533                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16534                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16535                    sscanf(message, "hint: %c", &c)!=1 &&
16536                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16537                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16538                     print = (appData.engineComments >= 2);
16539                 }
16540                 message[0] = start; // restore original message
16541         }
16542         if(print) {
16543                 GetTimeMark(&now);
16544                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16545                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16546                         quote,
16547                         message);
16548                 if(serverFP)
16549                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16550                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16551                         quote,
16552                         message), fflush(serverFP);
16553         }
16554     }
16555
16556     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16557     if (appData.icsEngineAnalyze) {
16558         if (strstr(message, "whisper") != NULL ||
16559              strstr(message, "kibitz") != NULL ||
16560             strstr(message, "tellics") != NULL) return;
16561     }
16562
16563     HandleMachineMove(message, cps);
16564 }
16565
16566
16567 void
16568 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16569 {
16570     char buf[MSG_SIZ];
16571     int seconds;
16572
16573     if( timeControl_2 > 0 ) {
16574         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16575             tc = timeControl_2;
16576         }
16577     }
16578     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16579     inc /= cps->timeOdds;
16580     st  /= cps->timeOdds;
16581
16582     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16583
16584     if (st > 0) {
16585       /* Set exact time per move, normally using st command */
16586       if (cps->stKludge) {
16587         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16588         seconds = st % 60;
16589         if (seconds == 0) {
16590           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16591         } else {
16592           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16593         }
16594       } else {
16595         snprintf(buf, MSG_SIZ, "st %d\n", st);
16596       }
16597     } else {
16598       /* Set conventional or incremental time control, using level command */
16599       if (seconds == 0) {
16600         /* Note old gnuchess bug -- minutes:seconds used to not work.
16601            Fixed in later versions, but still avoid :seconds
16602            when seconds is 0. */
16603         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16604       } else {
16605         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16606                  seconds, inc/1000.);
16607       }
16608     }
16609     SendToProgram(buf, cps);
16610
16611     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16612     /* Orthogonally, limit search to given depth */
16613     if (sd > 0) {
16614       if (cps->sdKludge) {
16615         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16616       } else {
16617         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16618       }
16619       SendToProgram(buf, cps);
16620     }
16621
16622     if(cps->nps >= 0) { /* [HGM] nps */
16623         if(cps->supportsNPS == FALSE)
16624           cps->nps = -1; // don't use if engine explicitly says not supported!
16625         else {
16626           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16627           SendToProgram(buf, cps);
16628         }
16629     }
16630 }
16631
16632 ChessProgramState *
16633 WhitePlayer ()
16634 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16635 {
16636     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16637        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16638         return &second;
16639     return &first;
16640 }
16641
16642 void
16643 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16644 {
16645     char message[MSG_SIZ];
16646     long time, otime;
16647
16648     /* Note: this routine must be called when the clocks are stopped
16649        or when they have *just* been set or switched; otherwise
16650        it will be off by the time since the current tick started.
16651     */
16652     if (machineWhite) {
16653         time = whiteTimeRemaining / 10;
16654         otime = blackTimeRemaining / 10;
16655     } else {
16656         time = blackTimeRemaining / 10;
16657         otime = whiteTimeRemaining / 10;
16658     }
16659     /* [HGM] translate opponent's time by time-odds factor */
16660     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16661
16662     if (time <= 0) time = 1;
16663     if (otime <= 0) otime = 1;
16664
16665     snprintf(message, MSG_SIZ, "time %ld\n", time);
16666     SendToProgram(message, cps);
16667
16668     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16669     SendToProgram(message, cps);
16670 }
16671
16672 char *
16673 EngineDefinedVariant (ChessProgramState *cps, int n)
16674 {   // return name of n-th unknown variant that engine supports
16675     static char buf[MSG_SIZ];
16676     char *p, *s = cps->variants;
16677     if(!s) return NULL;
16678     do { // parse string from variants feature
16679       VariantClass v;
16680         p = strchr(s, ',');
16681         if(p) *p = NULLCHAR;
16682       v = StringToVariant(s);
16683       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16684         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16685             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16686                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16687                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16688                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16689             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16690         }
16691         if(p) *p++ = ',';
16692         if(n < 0) return buf;
16693     } while(s = p);
16694     return NULL;
16695 }
16696
16697 int
16698 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16699 {
16700   char buf[MSG_SIZ];
16701   int len = strlen(name);
16702   int val;
16703
16704   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16705     (*p) += len + 1;
16706     sscanf(*p, "%d", &val);
16707     *loc = (val != 0);
16708     while (**p && **p != ' ')
16709       (*p)++;
16710     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16711     SendToProgram(buf, cps);
16712     return TRUE;
16713   }
16714   return FALSE;
16715 }
16716
16717 int
16718 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16719 {
16720   char buf[MSG_SIZ];
16721   int len = strlen(name);
16722   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16723     (*p) += len + 1;
16724     sscanf(*p, "%d", loc);
16725     while (**p && **p != ' ') (*p)++;
16726     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16727     SendToProgram(buf, cps);
16728     return TRUE;
16729   }
16730   return FALSE;
16731 }
16732
16733 int
16734 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16735 {
16736   char buf[MSG_SIZ];
16737   int len = strlen(name);
16738   if (strncmp((*p), name, len) == 0
16739       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16740     (*p) += len + 2;
16741     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16742     sscanf(*p, "%[^\"]", *loc);
16743     while (**p && **p != '\"') (*p)++;
16744     if (**p == '\"') (*p)++;
16745     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16746     SendToProgram(buf, cps);
16747     return TRUE;
16748   }
16749   return FALSE;
16750 }
16751
16752 int
16753 ParseOption (Option *opt, ChessProgramState *cps)
16754 // [HGM] options: process the string that defines an engine option, and determine
16755 // name, type, default value, and allowed value range
16756 {
16757         char *p, *q, buf[MSG_SIZ];
16758         int n, min = (-1)<<31, max = 1<<31, def;
16759
16760         if(p = strstr(opt->name, " -spin ")) {
16761             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16762             if(max < min) max = min; // enforce consistency
16763             if(def < min) def = min;
16764             if(def > max) def = max;
16765             opt->value = def;
16766             opt->min = min;
16767             opt->max = max;
16768             opt->type = Spin;
16769         } else if((p = strstr(opt->name, " -slider "))) {
16770             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16771             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16772             if(max < min) max = min; // enforce consistency
16773             if(def < min) def = min;
16774             if(def > max) def = max;
16775             opt->value = def;
16776             opt->min = min;
16777             opt->max = max;
16778             opt->type = Spin; // Slider;
16779         } else if((p = strstr(opt->name, " -string "))) {
16780             opt->textValue = p+9;
16781             opt->type = TextBox;
16782         } else if((p = strstr(opt->name, " -file "))) {
16783             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16784             opt->textValue = p+7;
16785             opt->type = FileName; // FileName;
16786         } else if((p = strstr(opt->name, " -path "))) {
16787             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16788             opt->textValue = p+7;
16789             opt->type = PathName; // PathName;
16790         } else if(p = strstr(opt->name, " -check ")) {
16791             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16792             opt->value = (def != 0);
16793             opt->type = CheckBox;
16794         } else if(p = strstr(opt->name, " -combo ")) {
16795             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16796             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16797             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16798             opt->value = n = 0;
16799             while(q = StrStr(q, " /// ")) {
16800                 n++; *q = 0;    // count choices, and null-terminate each of them
16801                 q += 5;
16802                 if(*q == '*') { // remember default, which is marked with * prefix
16803                     q++;
16804                     opt->value = n;
16805                 }
16806                 cps->comboList[cps->comboCnt++] = q;
16807             }
16808             cps->comboList[cps->comboCnt++] = NULL;
16809             opt->max = n + 1;
16810             opt->type = ComboBox;
16811         } else if(p = strstr(opt->name, " -button")) {
16812             opt->type = Button;
16813         } else if(p = strstr(opt->name, " -save")) {
16814             opt->type = SaveButton;
16815         } else return FALSE;
16816         *p = 0; // terminate option name
16817         // now look if the command-line options define a setting for this engine option.
16818         if(cps->optionSettings && cps->optionSettings[0])
16819             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16820         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16821           snprintf(buf, MSG_SIZ, "option %s", p);
16822                 if(p = strstr(buf, ",")) *p = 0;
16823                 if(q = strchr(buf, '=')) switch(opt->type) {
16824                     case ComboBox:
16825                         for(n=0; n<opt->max; n++)
16826                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16827                         break;
16828                     case TextBox:
16829                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16830                         break;
16831                     case Spin:
16832                     case CheckBox:
16833                         opt->value = atoi(q+1);
16834                     default:
16835                         break;
16836                 }
16837                 strcat(buf, "\n");
16838                 SendToProgram(buf, cps);
16839         }
16840         return TRUE;
16841 }
16842
16843 void
16844 FeatureDone (ChessProgramState *cps, int val)
16845 {
16846   DelayedEventCallback cb = GetDelayedEvent();
16847   if ((cb == InitBackEnd3 && cps == &first) ||
16848       (cb == SettingsMenuIfReady && cps == &second) ||
16849       (cb == LoadEngine) ||
16850       (cb == TwoMachinesEventIfReady)) {
16851     CancelDelayedEvent();
16852     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16853   }
16854   cps->initDone = val;
16855   if(val) cps->reload = FALSE;
16856 }
16857
16858 /* Parse feature command from engine */
16859 void
16860 ParseFeatures (char *args, ChessProgramState *cps)
16861 {
16862   char *p = args;
16863   char *q = NULL;
16864   int val;
16865   char buf[MSG_SIZ];
16866
16867   for (;;) {
16868     while (*p == ' ') p++;
16869     if (*p == NULLCHAR) return;
16870
16871     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16872     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16873     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16874     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16875     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16876     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16877     if (BoolFeature(&p, "reuse", &val, cps)) {
16878       /* Engine can disable reuse, but can't enable it if user said no */
16879       if (!val) cps->reuse = FALSE;
16880       continue;
16881     }
16882     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16883     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16884       if (gameMode == TwoMachinesPlay) {
16885         DisplayTwoMachinesTitle();
16886       } else {
16887         DisplayTitle("");
16888       }
16889       continue;
16890     }
16891     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16892     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16893     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16894     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16895     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16896     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16897     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16898     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16899     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16900     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16901     if (IntFeature(&p, "done", &val, cps)) {
16902       FeatureDone(cps, val);
16903       continue;
16904     }
16905     /* Added by Tord: */
16906     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16907     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16908     /* End of additions by Tord */
16909
16910     /* [HGM] added features: */
16911     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16912     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16913     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16914     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16915     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16916     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16917     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16918     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16919         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16920         FREE(cps->option[cps->nrOptions].name);
16921         cps->option[cps->nrOptions].name = q; q = NULL;
16922         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16923           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16924             SendToProgram(buf, cps);
16925             continue;
16926         }
16927         if(cps->nrOptions >= MAX_OPTIONS) {
16928             cps->nrOptions--;
16929             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16930             DisplayError(buf, 0);
16931         }
16932         continue;
16933     }
16934     /* End of additions by HGM */
16935
16936     /* unknown feature: complain and skip */
16937     q = p;
16938     while (*q && *q != '=') q++;
16939     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16940     SendToProgram(buf, cps);
16941     p = q;
16942     if (*p == '=') {
16943       p++;
16944       if (*p == '\"') {
16945         p++;
16946         while (*p && *p != '\"') p++;
16947         if (*p == '\"') p++;
16948       } else {
16949         while (*p && *p != ' ') p++;
16950       }
16951     }
16952   }
16953
16954 }
16955
16956 void
16957 PeriodicUpdatesEvent (int newState)
16958 {
16959     if (newState == appData.periodicUpdates)
16960       return;
16961
16962     appData.periodicUpdates=newState;
16963
16964     /* Display type changes, so update it now */
16965 //    DisplayAnalysis();
16966
16967     /* Get the ball rolling again... */
16968     if (newState) {
16969         AnalysisPeriodicEvent(1);
16970         StartAnalysisClock();
16971     }
16972 }
16973
16974 void
16975 PonderNextMoveEvent (int newState)
16976 {
16977     if (newState == appData.ponderNextMove) return;
16978     if (gameMode == EditPosition) EditPositionDone(TRUE);
16979     if (newState) {
16980         SendToProgram("hard\n", &first);
16981         if (gameMode == TwoMachinesPlay) {
16982             SendToProgram("hard\n", &second);
16983         }
16984     } else {
16985         SendToProgram("easy\n", &first);
16986         thinkOutput[0] = NULLCHAR;
16987         if (gameMode == TwoMachinesPlay) {
16988             SendToProgram("easy\n", &second);
16989         }
16990     }
16991     appData.ponderNextMove = newState;
16992 }
16993
16994 void
16995 NewSettingEvent (int option, int *feature, char *command, int value)
16996 {
16997     char buf[MSG_SIZ];
16998
16999     if (gameMode == EditPosition) EditPositionDone(TRUE);
17000     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17001     if(feature == NULL || *feature) SendToProgram(buf, &first);
17002     if (gameMode == TwoMachinesPlay) {
17003         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17004     }
17005 }
17006
17007 void
17008 ShowThinkingEvent ()
17009 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17010 {
17011     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17012     int newState = appData.showThinking
17013         // [HGM] thinking: other features now need thinking output as well
17014         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17015
17016     if (oldState == newState) return;
17017     oldState = newState;
17018     if (gameMode == EditPosition) EditPositionDone(TRUE);
17019     if (oldState) {
17020         SendToProgram("post\n", &first);
17021         if (gameMode == TwoMachinesPlay) {
17022             SendToProgram("post\n", &second);
17023         }
17024     } else {
17025         SendToProgram("nopost\n", &first);
17026         thinkOutput[0] = NULLCHAR;
17027         if (gameMode == TwoMachinesPlay) {
17028             SendToProgram("nopost\n", &second);
17029         }
17030     }
17031 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17032 }
17033
17034 void
17035 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17036 {
17037   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17038   if (pr == NoProc) return;
17039   AskQuestion(title, question, replyPrefix, pr);
17040 }
17041
17042 void
17043 TypeInEvent (char firstChar)
17044 {
17045     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17046         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17047         gameMode == AnalyzeMode || gameMode == EditGame ||
17048         gameMode == EditPosition || gameMode == IcsExamining ||
17049         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17050         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17051                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17052                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17053         gameMode == Training) PopUpMoveDialog(firstChar);
17054 }
17055
17056 void
17057 TypeInDoneEvent (char *move)
17058 {
17059         Board board;
17060         int n, fromX, fromY, toX, toY;
17061         char promoChar;
17062         ChessMove moveType;
17063
17064         // [HGM] FENedit
17065         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17066                 EditPositionPasteFEN(move);
17067                 return;
17068         }
17069         // [HGM] movenum: allow move number to be typed in any mode
17070         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17071           ToNrEvent(2*n-1);
17072           return;
17073         }
17074         // undocumented kludge: allow command-line option to be typed in!
17075         // (potentially fatal, and does not implement the effect of the option.)
17076         // should only be used for options that are values on which future decisions will be made,
17077         // and definitely not on options that would be used during initialization.
17078         if(strstr(move, "!!! -") == move) {
17079             ParseArgsFromString(move+4);
17080             return;
17081         }
17082
17083       if (gameMode != EditGame && currentMove != forwardMostMove &&
17084         gameMode != Training) {
17085         DisplayMoveError(_("Displayed move is not current"));
17086       } else {
17087         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17088           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17089         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17090         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17091           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17092           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17093         } else {
17094           DisplayMoveError(_("Could not parse move"));
17095         }
17096       }
17097 }
17098
17099 void
17100 DisplayMove (int moveNumber)
17101 {
17102     char message[MSG_SIZ];
17103     char res[MSG_SIZ];
17104     char cpThinkOutput[MSG_SIZ];
17105
17106     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17107
17108     if (moveNumber == forwardMostMove - 1 ||
17109         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17110
17111         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17112
17113         if (strchr(cpThinkOutput, '\n')) {
17114             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17115         }
17116     } else {
17117         *cpThinkOutput = NULLCHAR;
17118     }
17119
17120     /* [AS] Hide thinking from human user */
17121     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17122         *cpThinkOutput = NULLCHAR;
17123         if( thinkOutput[0] != NULLCHAR ) {
17124             int i;
17125
17126             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17127                 cpThinkOutput[i] = '.';
17128             }
17129             cpThinkOutput[i] = NULLCHAR;
17130             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17131         }
17132     }
17133
17134     if (moveNumber == forwardMostMove - 1 &&
17135         gameInfo.resultDetails != NULL) {
17136         if (gameInfo.resultDetails[0] == NULLCHAR) {
17137           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17138         } else {
17139           snprintf(res, MSG_SIZ, " {%s} %s",
17140                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17141         }
17142     } else {
17143         res[0] = NULLCHAR;
17144     }
17145
17146     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17147         DisplayMessage(res, cpThinkOutput);
17148     } else {
17149       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17150                 WhiteOnMove(moveNumber) ? " " : ".. ",
17151                 parseList[moveNumber], res);
17152         DisplayMessage(message, cpThinkOutput);
17153     }
17154 }
17155
17156 void
17157 DisplayComment (int moveNumber, char *text)
17158 {
17159     char title[MSG_SIZ];
17160
17161     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17162       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17163     } else {
17164       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17165               WhiteOnMove(moveNumber) ? " " : ".. ",
17166               parseList[moveNumber]);
17167     }
17168     if (text != NULL && (appData.autoDisplayComment || commentUp))
17169         CommentPopUp(title, text);
17170 }
17171
17172 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17173  * might be busy thinking or pondering.  It can be omitted if your
17174  * gnuchess is configured to stop thinking immediately on any user
17175  * input.  However, that gnuchess feature depends on the FIONREAD
17176  * ioctl, which does not work properly on some flavors of Unix.
17177  */
17178 void
17179 Attention (ChessProgramState *cps)
17180 {
17181 #if ATTENTION
17182     if (!cps->useSigint) return;
17183     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17184     switch (gameMode) {
17185       case MachinePlaysWhite:
17186       case MachinePlaysBlack:
17187       case TwoMachinesPlay:
17188       case IcsPlayingWhite:
17189       case IcsPlayingBlack:
17190       case AnalyzeMode:
17191       case AnalyzeFile:
17192         /* Skip if we know it isn't thinking */
17193         if (!cps->maybeThinking) return;
17194         if (appData.debugMode)
17195           fprintf(debugFP, "Interrupting %s\n", cps->which);
17196         InterruptChildProcess(cps->pr);
17197         cps->maybeThinking = FALSE;
17198         break;
17199       default:
17200         break;
17201     }
17202 #endif /*ATTENTION*/
17203 }
17204
17205 int
17206 CheckFlags ()
17207 {
17208     if (whiteTimeRemaining <= 0) {
17209         if (!whiteFlag) {
17210             whiteFlag = TRUE;
17211             if (appData.icsActive) {
17212                 if (appData.autoCallFlag &&
17213                     gameMode == IcsPlayingBlack && !blackFlag) {
17214                   SendToICS(ics_prefix);
17215                   SendToICS("flag\n");
17216                 }
17217             } else {
17218                 if (blackFlag) {
17219                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17220                 } else {
17221                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17222                     if (appData.autoCallFlag) {
17223                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17224                         return TRUE;
17225                     }
17226                 }
17227             }
17228         }
17229     }
17230     if (blackTimeRemaining <= 0) {
17231         if (!blackFlag) {
17232             blackFlag = TRUE;
17233             if (appData.icsActive) {
17234                 if (appData.autoCallFlag &&
17235                     gameMode == IcsPlayingWhite && !whiteFlag) {
17236                   SendToICS(ics_prefix);
17237                   SendToICS("flag\n");
17238                 }
17239             } else {
17240                 if (whiteFlag) {
17241                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17242                 } else {
17243                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17244                     if (appData.autoCallFlag) {
17245                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17246                         return TRUE;
17247                     }
17248                 }
17249             }
17250         }
17251     }
17252     return FALSE;
17253 }
17254
17255 void
17256 CheckTimeControl ()
17257 {
17258     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17259         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17260
17261     /*
17262      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17263      */
17264     if ( !WhiteOnMove(forwardMostMove) ) {
17265         /* White made time control */
17266         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17267         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17268         /* [HGM] time odds: correct new time quota for time odds! */
17269                                             / WhitePlayer()->timeOdds;
17270         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17271     } else {
17272         lastBlack -= blackTimeRemaining;
17273         /* Black made time control */
17274         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17275                                             / WhitePlayer()->other->timeOdds;
17276         lastWhite = whiteTimeRemaining;
17277     }
17278 }
17279
17280 void
17281 DisplayBothClocks ()
17282 {
17283     int wom = gameMode == EditPosition ?
17284       !blackPlaysFirst : WhiteOnMove(currentMove);
17285     DisplayWhiteClock(whiteTimeRemaining, wom);
17286     DisplayBlackClock(blackTimeRemaining, !wom);
17287 }
17288
17289
17290 /* Timekeeping seems to be a portability nightmare.  I think everyone
17291    has ftime(), but I'm really not sure, so I'm including some ifdefs
17292    to use other calls if you don't.  Clocks will be less accurate if
17293    you have neither ftime nor gettimeofday.
17294 */
17295
17296 /* VS 2008 requires the #include outside of the function */
17297 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17298 #include <sys/timeb.h>
17299 #endif
17300
17301 /* Get the current time as a TimeMark */
17302 void
17303 GetTimeMark (TimeMark *tm)
17304 {
17305 #if HAVE_GETTIMEOFDAY
17306
17307     struct timeval timeVal;
17308     struct timezone timeZone;
17309
17310     gettimeofday(&timeVal, &timeZone);
17311     tm->sec = (long) timeVal.tv_sec;
17312     tm->ms = (int) (timeVal.tv_usec / 1000L);
17313
17314 #else /*!HAVE_GETTIMEOFDAY*/
17315 #if HAVE_FTIME
17316
17317 // include <sys/timeb.h> / moved to just above start of function
17318     struct timeb timeB;
17319
17320     ftime(&timeB);
17321     tm->sec = (long) timeB.time;
17322     tm->ms = (int) timeB.millitm;
17323
17324 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17325     tm->sec = (long) time(NULL);
17326     tm->ms = 0;
17327 #endif
17328 #endif
17329 }
17330
17331 /* Return the difference in milliseconds between two
17332    time marks.  We assume the difference will fit in a long!
17333 */
17334 long
17335 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17336 {
17337     return 1000L*(tm2->sec - tm1->sec) +
17338            (long) (tm2->ms - tm1->ms);
17339 }
17340
17341
17342 /*
17343  * Code to manage the game clocks.
17344  *
17345  * In tournament play, black starts the clock and then white makes a move.
17346  * We give the human user a slight advantage if he is playing white---the
17347  * clocks don't run until he makes his first move, so it takes zero time.
17348  * Also, we don't account for network lag, so we could get out of sync
17349  * with GNU Chess's clock -- but then, referees are always right.
17350  */
17351
17352 static TimeMark tickStartTM;
17353 static long intendedTickLength;
17354
17355 long
17356 NextTickLength (long timeRemaining)
17357 {
17358     long nominalTickLength, nextTickLength;
17359
17360     if (timeRemaining > 0L && timeRemaining <= 10000L)
17361       nominalTickLength = 100L;
17362     else
17363       nominalTickLength = 1000L;
17364     nextTickLength = timeRemaining % nominalTickLength;
17365     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17366
17367     return nextTickLength;
17368 }
17369
17370 /* Adjust clock one minute up or down */
17371 void
17372 AdjustClock (Boolean which, int dir)
17373 {
17374     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17375     if(which) blackTimeRemaining += 60000*dir;
17376     else      whiteTimeRemaining += 60000*dir;
17377     DisplayBothClocks();
17378     adjustedClock = TRUE;
17379 }
17380
17381 /* Stop clocks and reset to a fresh time control */
17382 void
17383 ResetClocks ()
17384 {
17385     (void) StopClockTimer();
17386     if (appData.icsActive) {
17387         whiteTimeRemaining = blackTimeRemaining = 0;
17388     } else if (searchTime) {
17389         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17390         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17391     } else { /* [HGM] correct new time quote for time odds */
17392         whiteTC = blackTC = fullTimeControlString;
17393         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17394         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17395     }
17396     if (whiteFlag || blackFlag) {
17397         DisplayTitle("");
17398         whiteFlag = blackFlag = FALSE;
17399     }
17400     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17401     DisplayBothClocks();
17402     adjustedClock = FALSE;
17403 }
17404
17405 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17406
17407 /* Decrement running clock by amount of time that has passed */
17408 void
17409 DecrementClocks ()
17410 {
17411     long timeRemaining;
17412     long lastTickLength, fudge;
17413     TimeMark now;
17414
17415     if (!appData.clockMode) return;
17416     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17417
17418     GetTimeMark(&now);
17419
17420     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17421
17422     /* Fudge if we woke up a little too soon */
17423     fudge = intendedTickLength - lastTickLength;
17424     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17425
17426     if (WhiteOnMove(forwardMostMove)) {
17427         if(whiteNPS >= 0) lastTickLength = 0;
17428         timeRemaining = whiteTimeRemaining -= lastTickLength;
17429         if(timeRemaining < 0 && !appData.icsActive) {
17430             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17431             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17432                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17433                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17434             }
17435         }
17436         DisplayWhiteClock(whiteTimeRemaining - fudge,
17437                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17438     } else {
17439         if(blackNPS >= 0) lastTickLength = 0;
17440         timeRemaining = blackTimeRemaining -= lastTickLength;
17441         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17442             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17443             if(suddenDeath) {
17444                 blackStartMove = forwardMostMove;
17445                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17446             }
17447         }
17448         DisplayBlackClock(blackTimeRemaining - fudge,
17449                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17450     }
17451     if (CheckFlags()) return;
17452
17453     if(twoBoards) { // count down secondary board's clocks as well
17454         activePartnerTime -= lastTickLength;
17455         partnerUp = 1;
17456         if(activePartner == 'W')
17457             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17458         else
17459             DisplayBlackClock(activePartnerTime, TRUE);
17460         partnerUp = 0;
17461     }
17462
17463     tickStartTM = now;
17464     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17465     StartClockTimer(intendedTickLength);
17466
17467     /* if the time remaining has fallen below the alarm threshold, sound the
17468      * alarm. if the alarm has sounded and (due to a takeback or time control
17469      * with increment) the time remaining has increased to a level above the
17470      * threshold, reset the alarm so it can sound again.
17471      */
17472
17473     if (appData.icsActive && appData.icsAlarm) {
17474
17475         /* make sure we are dealing with the user's clock */
17476         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17477                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17478            )) return;
17479
17480         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17481             alarmSounded = FALSE;
17482         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17483             PlayAlarmSound();
17484             alarmSounded = TRUE;
17485         }
17486     }
17487 }
17488
17489
17490 /* A player has just moved, so stop the previously running
17491    clock and (if in clock mode) start the other one.
17492    We redisplay both clocks in case we're in ICS mode, because
17493    ICS gives us an update to both clocks after every move.
17494    Note that this routine is called *after* forwardMostMove
17495    is updated, so the last fractional tick must be subtracted
17496    from the color that is *not* on move now.
17497 */
17498 void
17499 SwitchClocks (int newMoveNr)
17500 {
17501     long lastTickLength;
17502     TimeMark now;
17503     int flagged = FALSE;
17504
17505     GetTimeMark(&now);
17506
17507     if (StopClockTimer() && appData.clockMode) {
17508         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17509         if (!WhiteOnMove(forwardMostMove)) {
17510             if(blackNPS >= 0) lastTickLength = 0;
17511             blackTimeRemaining -= lastTickLength;
17512            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17513 //         if(pvInfoList[forwardMostMove].time == -1)
17514                  pvInfoList[forwardMostMove].time =               // use GUI time
17515                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17516         } else {
17517            if(whiteNPS >= 0) lastTickLength = 0;
17518            whiteTimeRemaining -= lastTickLength;
17519            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17520 //         if(pvInfoList[forwardMostMove].time == -1)
17521                  pvInfoList[forwardMostMove].time =
17522                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17523         }
17524         flagged = CheckFlags();
17525     }
17526     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17527     CheckTimeControl();
17528
17529     if (flagged || !appData.clockMode) return;
17530
17531     switch (gameMode) {
17532       case MachinePlaysBlack:
17533       case MachinePlaysWhite:
17534       case BeginningOfGame:
17535         if (pausing) return;
17536         break;
17537
17538       case EditGame:
17539       case PlayFromGameFile:
17540       case IcsExamining:
17541         return;
17542
17543       default:
17544         break;
17545     }
17546
17547     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17548         if(WhiteOnMove(forwardMostMove))
17549              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17550         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17551     }
17552
17553     tickStartTM = now;
17554     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17555       whiteTimeRemaining : blackTimeRemaining);
17556     StartClockTimer(intendedTickLength);
17557 }
17558
17559
17560 /* Stop both clocks */
17561 void
17562 StopClocks ()
17563 {
17564     long lastTickLength;
17565     TimeMark now;
17566
17567     if (!StopClockTimer()) return;
17568     if (!appData.clockMode) return;
17569
17570     GetTimeMark(&now);
17571
17572     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17573     if (WhiteOnMove(forwardMostMove)) {
17574         if(whiteNPS >= 0) lastTickLength = 0;
17575         whiteTimeRemaining -= lastTickLength;
17576         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17577     } else {
17578         if(blackNPS >= 0) lastTickLength = 0;
17579         blackTimeRemaining -= lastTickLength;
17580         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17581     }
17582     CheckFlags();
17583 }
17584
17585 /* Start clock of player on move.  Time may have been reset, so
17586    if clock is already running, stop and restart it. */
17587 void
17588 StartClocks ()
17589 {
17590     (void) StopClockTimer(); /* in case it was running already */
17591     DisplayBothClocks();
17592     if (CheckFlags()) return;
17593
17594     if (!appData.clockMode) return;
17595     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17596
17597     GetTimeMark(&tickStartTM);
17598     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17599       whiteTimeRemaining : blackTimeRemaining);
17600
17601    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17602     whiteNPS = blackNPS = -1;
17603     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17604        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17605         whiteNPS = first.nps;
17606     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17607        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17608         blackNPS = first.nps;
17609     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17610         whiteNPS = second.nps;
17611     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17612         blackNPS = second.nps;
17613     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17614
17615     StartClockTimer(intendedTickLength);
17616 }
17617
17618 char *
17619 TimeString (long ms)
17620 {
17621     long second, minute, hour, day;
17622     char *sign = "";
17623     static char buf[32];
17624
17625     if (ms > 0 && ms <= 9900) {
17626       /* convert milliseconds to tenths, rounding up */
17627       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17628
17629       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17630       return buf;
17631     }
17632
17633     /* convert milliseconds to seconds, rounding up */
17634     /* use floating point to avoid strangeness of integer division
17635        with negative dividends on many machines */
17636     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17637
17638     if (second < 0) {
17639         sign = "-";
17640         second = -second;
17641     }
17642
17643     day = second / (60 * 60 * 24);
17644     second = second % (60 * 60 * 24);
17645     hour = second / (60 * 60);
17646     second = second % (60 * 60);
17647     minute = second / 60;
17648     second = second % 60;
17649
17650     if (day > 0)
17651       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17652               sign, day, hour, minute, second);
17653     else if (hour > 0)
17654       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17655     else
17656       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17657
17658     return buf;
17659 }
17660
17661
17662 /*
17663  * This is necessary because some C libraries aren't ANSI C compliant yet.
17664  */
17665 char *
17666 StrStr (char *string, char *match)
17667 {
17668     int i, length;
17669
17670     length = strlen(match);
17671
17672     for (i = strlen(string) - length; i >= 0; i--, string++)
17673       if (!strncmp(match, string, length))
17674         return string;
17675
17676     return NULL;
17677 }
17678
17679 char *
17680 StrCaseStr (char *string, char *match)
17681 {
17682     int i, j, length;
17683
17684     length = strlen(match);
17685
17686     for (i = strlen(string) - length; i >= 0; i--, string++) {
17687         for (j = 0; j < length; j++) {
17688             if (ToLower(match[j]) != ToLower(string[j]))
17689               break;
17690         }
17691         if (j == length) return string;
17692     }
17693
17694     return NULL;
17695 }
17696
17697 #ifndef _amigados
17698 int
17699 StrCaseCmp (char *s1, char *s2)
17700 {
17701     char c1, c2;
17702
17703     for (;;) {
17704         c1 = ToLower(*s1++);
17705         c2 = ToLower(*s2++);
17706         if (c1 > c2) return 1;
17707         if (c1 < c2) return -1;
17708         if (c1 == NULLCHAR) return 0;
17709     }
17710 }
17711
17712
17713 int
17714 ToLower (int c)
17715 {
17716     return isupper(c) ? tolower(c) : c;
17717 }
17718
17719
17720 int
17721 ToUpper (int c)
17722 {
17723     return islower(c) ? toupper(c) : c;
17724 }
17725 #endif /* !_amigados    */
17726
17727 char *
17728 StrSave (char *s)
17729 {
17730   char *ret;
17731
17732   if ((ret = (char *) malloc(strlen(s) + 1)))
17733     {
17734       safeStrCpy(ret, s, strlen(s)+1);
17735     }
17736   return ret;
17737 }
17738
17739 char *
17740 StrSavePtr (char *s, char **savePtr)
17741 {
17742     if (*savePtr) {
17743         free(*savePtr);
17744     }
17745     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17746       safeStrCpy(*savePtr, s, strlen(s)+1);
17747     }
17748     return(*savePtr);
17749 }
17750
17751 char *
17752 PGNDate ()
17753 {
17754     time_t clock;
17755     struct tm *tm;
17756     char buf[MSG_SIZ];
17757
17758     clock = time((time_t *)NULL);
17759     tm = localtime(&clock);
17760     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17761             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17762     return StrSave(buf);
17763 }
17764
17765
17766 char *
17767 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17768 {
17769     int i, j, fromX, fromY, toX, toY;
17770     int whiteToPlay;
17771     char buf[MSG_SIZ];
17772     char *p, *q;
17773     int emptycount;
17774     ChessSquare piece;
17775
17776     whiteToPlay = (gameMode == EditPosition) ?
17777       !blackPlaysFirst : (move % 2 == 0);
17778     p = buf;
17779
17780     /* Piece placement data */
17781     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17782         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17783         emptycount = 0;
17784         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17785             if (boards[move][i][j] == EmptySquare) {
17786                 emptycount++;
17787             } else { ChessSquare piece = boards[move][i][j];
17788                 if (emptycount > 0) {
17789                     if(emptycount<10) /* [HGM] can be >= 10 */
17790                         *p++ = '0' + emptycount;
17791                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17792                     emptycount = 0;
17793                 }
17794                 if(PieceToChar(piece) == '+') {
17795                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17796                     *p++ = '+';
17797                     piece = (ChessSquare)(CHUDEMOTED piece);
17798                 }
17799                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17800                 if(p[-1] == '~') {
17801                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17802                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17803                     *p++ = '~';
17804                 }
17805             }
17806         }
17807         if (emptycount > 0) {
17808             if(emptycount<10) /* [HGM] can be >= 10 */
17809                 *p++ = '0' + emptycount;
17810             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17811             emptycount = 0;
17812         }
17813         *p++ = '/';
17814     }
17815     *(p - 1) = ' ';
17816
17817     /* [HGM] print Crazyhouse or Shogi holdings */
17818     if( gameInfo.holdingsWidth ) {
17819         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17820         q = p;
17821         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17822             piece = boards[move][i][BOARD_WIDTH-1];
17823             if( piece != EmptySquare )
17824               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17825                   *p++ = PieceToChar(piece);
17826         }
17827         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17828             piece = boards[move][BOARD_HEIGHT-i-1][0];
17829             if( piece != EmptySquare )
17830               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17831                   *p++ = PieceToChar(piece);
17832         }
17833
17834         if( q == p ) *p++ = '-';
17835         *p++ = ']';
17836         *p++ = ' ';
17837     }
17838
17839     /* Active color */
17840     *p++ = whiteToPlay ? 'w' : 'b';
17841     *p++ = ' ';
17842
17843   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17844     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17845   } else {
17846   if(nrCastlingRights) {
17847      int handW=0, handB=0;
17848      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
17849         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
17850         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17851      }
17852      q = p;
17853      if(appData.fischerCastling) {
17854         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
17855            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17856                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17857         } else {
17858        /* [HGM] write directly from rights */
17859            if(boards[move][CASTLING][2] != NoRights &&
17860               boards[move][CASTLING][0] != NoRights   )
17861                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17862            if(boards[move][CASTLING][2] != NoRights &&
17863               boards[move][CASTLING][1] != NoRights   )
17864                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17865         }
17866         if(handB) {
17867            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17868                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17869         } else {
17870            if(boards[move][CASTLING][5] != NoRights &&
17871               boards[move][CASTLING][3] != NoRights   )
17872                 *p++ = boards[move][CASTLING][3] + AAA;
17873            if(boards[move][CASTLING][5] != NoRights &&
17874               boards[move][CASTLING][4] != NoRights   )
17875                 *p++ = boards[move][CASTLING][4] + AAA;
17876         }
17877      } else {
17878
17879         /* [HGM] write true castling rights */
17880         if( nrCastlingRights == 6 ) {
17881             int q, k=0;
17882             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17883                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17884             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17885                  boards[move][CASTLING][2] != NoRights  );
17886             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
17887                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17888                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17889                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17890             }
17891             if(q) *p++ = 'Q';
17892             k = 0;
17893             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17894                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17895             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17896                  boards[move][CASTLING][5] != NoRights  );
17897             if(handB) {
17898                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17899                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17900                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17901             }
17902             if(q) *p++ = 'q';
17903         }
17904      }
17905      if (q == p) *p++ = '-'; /* No castling rights */
17906      *p++ = ' ';
17907   }
17908
17909   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17910      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17911      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17912     /* En passant target square */
17913     if (move > backwardMostMove) {
17914         fromX = moveList[move - 1][0] - AAA;
17915         fromY = moveList[move - 1][1] - ONE;
17916         toX = moveList[move - 1][2] - AAA;
17917         toY = moveList[move - 1][3] - ONE;
17918         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17919             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17920             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17921             fromX == toX) {
17922             /* 2-square pawn move just happened */
17923             *p++ = toX + AAA;
17924             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17925         } else {
17926             *p++ = '-';
17927         }
17928     } else if(move == backwardMostMove) {
17929         // [HGM] perhaps we should always do it like this, and forget the above?
17930         if((signed char)boards[move][EP_STATUS] >= 0) {
17931             *p++ = boards[move][EP_STATUS] + AAA;
17932             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17933         } else {
17934             *p++ = '-';
17935         }
17936     } else {
17937         *p++ = '-';
17938     }
17939     *p++ = ' ';
17940   }
17941   }
17942
17943     if(moveCounts)
17944     {   int i = 0, j=move;
17945
17946         /* [HGM] find reversible plies */
17947         if (appData.debugMode) { int k;
17948             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17949             for(k=backwardMostMove; k<=forwardMostMove; k++)
17950                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17951
17952         }
17953
17954         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17955         if( j == backwardMostMove ) i += initialRulePlies;
17956         sprintf(p, "%d ", i);
17957         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17958
17959         /* Fullmove number */
17960         sprintf(p, "%d", (move / 2) + 1);
17961     } else *--p = NULLCHAR;
17962
17963     return StrSave(buf);
17964 }
17965
17966 Boolean
17967 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17968 {
17969     int i, j, k, w=0, subst=0, shuffle=0;
17970     char *p, c;
17971     int emptycount, virgin[BOARD_FILES];
17972     ChessSquare piece;
17973
17974     p = fen;
17975
17976     /* Piece placement data */
17977     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17978         j = 0;
17979         for (;;) {
17980             if (*p == '/' || *p == ' ' || *p == '[' ) {
17981                 if(j > w) w = j;
17982                 emptycount = gameInfo.boardWidth - j;
17983                 while (emptycount--)
17984                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17985                 if (*p == '/') p++;
17986                 else if(autoSize) { // we stumbled unexpectedly into end of board
17987                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17988                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17989                     }
17990                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17991                 }
17992                 break;
17993 #if(BOARD_FILES >= 10)*0
17994             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17995                 p++; emptycount=10;
17996                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17997                 while (emptycount--)
17998                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17999 #endif
18000             } else if (*p == '*') {
18001                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18002             } else if (isdigit(*p)) {
18003                 emptycount = *p++ - '0';
18004                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18005                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18006                 while (emptycount--)
18007                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18008             } else if (*p == '<') {
18009                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18010                 else if (i != 0 || !shuffle) return FALSE;
18011                 p++;
18012             } else if (shuffle && *p == '>') {
18013                 p++; // for now ignore closing shuffle range, and assume rank-end
18014             } else if (*p == '?') {
18015                 if (j >= gameInfo.boardWidth) return FALSE;
18016                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18017                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18018             } else if (*p == '+' || isalpha(*p)) {
18019                 if (j >= gameInfo.boardWidth) return FALSE;
18020                 if(*p=='+') {
18021                     piece = CharToPiece(*++p);
18022                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18023                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
18024                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18025                 } else piece = CharToPiece(*p++);
18026
18027                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18028                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18029                     piece = (ChessSquare) (PROMOTED piece);
18030                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18031                     p++;
18032                 }
18033                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18034             } else {
18035                 return FALSE;
18036             }
18037         }
18038     }
18039     while (*p == '/' || *p == ' ') p++;
18040
18041     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
18042
18043     /* [HGM] by default clear Crazyhouse holdings, if present */
18044     if(gameInfo.holdingsWidth) {
18045        for(i=0; i<BOARD_HEIGHT; i++) {
18046            board[i][0]             = EmptySquare; /* black holdings */
18047            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18048            board[i][1]             = (ChessSquare) 0; /* black counts */
18049            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18050        }
18051     }
18052
18053     /* [HGM] look for Crazyhouse holdings here */
18054     while(*p==' ') p++;
18055     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18056         int swap=0, wcnt=0, bcnt=0;
18057         if(*p == '[') p++;
18058         if(*p == '<') swap++, p++;
18059         if(*p == '-' ) p++; /* empty holdings */ else {
18060             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18061             /* if we would allow FEN reading to set board size, we would   */
18062             /* have to add holdings and shift the board read so far here   */
18063             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18064                 p++;
18065                 if((int) piece >= (int) BlackPawn ) {
18066                     i = (int)piece - (int)BlackPawn;
18067                     i = PieceToNumber((ChessSquare)i);
18068                     if( i >= gameInfo.holdingsSize ) return FALSE;
18069                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18070                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18071                     bcnt++;
18072                 } else {
18073                     i = (int)piece - (int)WhitePawn;
18074                     i = PieceToNumber((ChessSquare)i);
18075                     if( i >= gameInfo.holdingsSize ) return FALSE;
18076                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18077                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18078                     wcnt++;
18079                 }
18080             }
18081             if(subst) { // substitute back-rank question marks by holdings pieces
18082                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18083                     int k, m, n = bcnt + 1;
18084                     if(board[0][j] == ClearBoard) {
18085                         if(!wcnt) return FALSE;
18086                         n = rand() % wcnt;
18087                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18088                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18089                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18090                             break;
18091                         }
18092                     }
18093                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18094                         if(!bcnt) return FALSE;
18095                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18096                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18097                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18098                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18099                             break;
18100                         }
18101                     }
18102                 }
18103                 subst = 0;
18104             }
18105         }
18106         if(*p == ']') p++;
18107     }
18108
18109     if(subst) return FALSE; // substitution requested, but no holdings
18110
18111     while(*p == ' ') p++;
18112
18113     /* Active color */
18114     c = *p++;
18115     if(appData.colorNickNames) {
18116       if( c == appData.colorNickNames[0] ) c = 'w'; else
18117       if( c == appData.colorNickNames[1] ) c = 'b';
18118     }
18119     switch (c) {
18120       case 'w':
18121         *blackPlaysFirst = FALSE;
18122         break;
18123       case 'b':
18124         *blackPlaysFirst = TRUE;
18125         break;
18126       default:
18127         return FALSE;
18128     }
18129
18130     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18131     /* return the extra info in global variiables             */
18132
18133     /* set defaults in case FEN is incomplete */
18134     board[EP_STATUS] = EP_UNKNOWN;
18135     for(i=0; i<nrCastlingRights; i++ ) {
18136         board[CASTLING][i] =
18137             appData.fischerCastling ? NoRights : initialRights[i];
18138     }   /* assume possible unless obviously impossible */
18139     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18140     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18141     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18142                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18143     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18144     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18145     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18146                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18147     FENrulePlies = 0;
18148
18149     while(*p==' ') p++;
18150     if(nrCastlingRights) {
18151       int fischer = 0;
18152       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18153       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18154           /* castling indicator present, so default becomes no castlings */
18155           for(i=0; i<nrCastlingRights; i++ ) {
18156                  board[CASTLING][i] = NoRights;
18157           }
18158       }
18159       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18160              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18161              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18162              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18163         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18164
18165         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18166             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
18167             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
18168         }
18169         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18170             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18171         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18172                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18173         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18174                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18175         switch(c) {
18176           case'K':
18177               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
18178               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18179               board[CASTLING][2] = whiteKingFile;
18180               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18181               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18182               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18183               break;
18184           case'Q':
18185               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
18186               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18187               board[CASTLING][2] = whiteKingFile;
18188               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18189               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18190               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18191               break;
18192           case'k':
18193               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18194               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18195               board[CASTLING][5] = blackKingFile;
18196               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18197               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18198               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18199               break;
18200           case'q':
18201               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18202               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18203               board[CASTLING][5] = blackKingFile;
18204               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18205               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18206               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18207           case '-':
18208               break;
18209           default: /* FRC castlings */
18210               if(c >= 'a') { /* black rights */
18211                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18212                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18213                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18214                   if(i == BOARD_RGHT) break;
18215                   board[CASTLING][5] = i;
18216                   c -= AAA;
18217                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18218                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18219                   if(c > i)
18220                       board[CASTLING][3] = c;
18221                   else
18222                       board[CASTLING][4] = c;
18223               } else { /* white rights */
18224                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18225                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18226                     if(board[0][i] == WhiteKing) break;
18227                   if(i == BOARD_RGHT) break;
18228                   board[CASTLING][2] = i;
18229                   c -= AAA - 'a' + 'A';
18230                   if(board[0][c] >= WhiteKing) break;
18231                   if(c > i)
18232                       board[CASTLING][0] = c;
18233                   else
18234                       board[CASTLING][1] = c;
18235               }
18236         }
18237       }
18238       for(i=0; i<nrCastlingRights; i++)
18239         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18240       if(gameInfo.variant == VariantSChess)
18241         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18242       if(fischer && shuffle) appData.fischerCastling = TRUE;
18243     if (appData.debugMode) {
18244         fprintf(debugFP, "FEN castling rights:");
18245         for(i=0; i<nrCastlingRights; i++)
18246         fprintf(debugFP, " %d", board[CASTLING][i]);
18247         fprintf(debugFP, "\n");
18248     }
18249
18250       while(*p==' ') p++;
18251     }
18252
18253     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18254
18255     /* read e.p. field in games that know e.p. capture */
18256     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18257        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18258        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18259       if(*p=='-') {
18260         p++; board[EP_STATUS] = EP_NONE;
18261       } else {
18262          char c = *p++ - AAA;
18263
18264          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18265          if(*p >= '0' && *p <='9') p++;
18266          board[EP_STATUS] = c;
18267       }
18268     }
18269
18270
18271     if(sscanf(p, "%d", &i) == 1) {
18272         FENrulePlies = i; /* 50-move ply counter */
18273         /* (The move number is still ignored)    */
18274     }
18275
18276     return TRUE;
18277 }
18278
18279 void
18280 EditPositionPasteFEN (char *fen)
18281 {
18282   if (fen != NULL) {
18283     Board initial_position;
18284
18285     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18286       DisplayError(_("Bad FEN position in clipboard"), 0);
18287       return ;
18288     } else {
18289       int savedBlackPlaysFirst = blackPlaysFirst;
18290       EditPositionEvent();
18291       blackPlaysFirst = savedBlackPlaysFirst;
18292       CopyBoard(boards[0], initial_position);
18293       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18294       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18295       DisplayBothClocks();
18296       DrawPosition(FALSE, boards[currentMove]);
18297     }
18298   }
18299 }
18300
18301 static char cseq[12] = "\\   ";
18302
18303 Boolean
18304 set_cont_sequence (char *new_seq)
18305 {
18306     int len;
18307     Boolean ret;
18308
18309     // handle bad attempts to set the sequence
18310         if (!new_seq)
18311                 return 0; // acceptable error - no debug
18312
18313     len = strlen(new_seq);
18314     ret = (len > 0) && (len < sizeof(cseq));
18315     if (ret)
18316       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18317     else if (appData.debugMode)
18318       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18319     return ret;
18320 }
18321
18322 /*
18323     reformat a source message so words don't cross the width boundary.  internal
18324     newlines are not removed.  returns the wrapped size (no null character unless
18325     included in source message).  If dest is NULL, only calculate the size required
18326     for the dest buffer.  lp argument indicats line position upon entry, and it's
18327     passed back upon exit.
18328 */
18329 int
18330 wrap (char *dest, char *src, int count, int width, int *lp)
18331 {
18332     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18333
18334     cseq_len = strlen(cseq);
18335     old_line = line = *lp;
18336     ansi = len = clen = 0;
18337
18338     for (i=0; i < count; i++)
18339     {
18340         if (src[i] == '\033')
18341             ansi = 1;
18342
18343         // if we hit the width, back up
18344         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18345         {
18346             // store i & len in case the word is too long
18347             old_i = i, old_len = len;
18348
18349             // find the end of the last word
18350             while (i && src[i] != ' ' && src[i] != '\n')
18351             {
18352                 i--;
18353                 len--;
18354             }
18355
18356             // word too long?  restore i & len before splitting it
18357             if ((old_i-i+clen) >= width)
18358             {
18359                 i = old_i;
18360                 len = old_len;
18361             }
18362
18363             // extra space?
18364             if (i && src[i-1] == ' ')
18365                 len--;
18366
18367             if (src[i] != ' ' && src[i] != '\n')
18368             {
18369                 i--;
18370                 if (len)
18371                     len--;
18372             }
18373
18374             // now append the newline and continuation sequence
18375             if (dest)
18376                 dest[len] = '\n';
18377             len++;
18378             if (dest)
18379                 strncpy(dest+len, cseq, cseq_len);
18380             len += cseq_len;
18381             line = cseq_len;
18382             clen = cseq_len;
18383             continue;
18384         }
18385
18386         if (dest)
18387             dest[len] = src[i];
18388         len++;
18389         if (!ansi)
18390             line++;
18391         if (src[i] == '\n')
18392             line = 0;
18393         if (src[i] == 'm')
18394             ansi = 0;
18395     }
18396     if (dest && appData.debugMode)
18397     {
18398         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18399             count, width, line, len, *lp);
18400         show_bytes(debugFP, src, count);
18401         fprintf(debugFP, "\ndest: ");
18402         show_bytes(debugFP, dest, len);
18403         fprintf(debugFP, "\n");
18404     }
18405     *lp = dest ? line : old_line;
18406
18407     return len;
18408 }
18409
18410 // [HGM] vari: routines for shelving variations
18411 Boolean modeRestore = FALSE;
18412
18413 void
18414 PushInner (int firstMove, int lastMove)
18415 {
18416         int i, j, nrMoves = lastMove - firstMove;
18417
18418         // push current tail of game on stack
18419         savedResult[storedGames] = gameInfo.result;
18420         savedDetails[storedGames] = gameInfo.resultDetails;
18421         gameInfo.resultDetails = NULL;
18422         savedFirst[storedGames] = firstMove;
18423         savedLast [storedGames] = lastMove;
18424         savedFramePtr[storedGames] = framePtr;
18425         framePtr -= nrMoves; // reserve space for the boards
18426         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18427             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18428             for(j=0; j<MOVE_LEN; j++)
18429                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18430             for(j=0; j<2*MOVE_LEN; j++)
18431                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18432             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18433             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18434             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18435             pvInfoList[firstMove+i-1].depth = 0;
18436             commentList[framePtr+i] = commentList[firstMove+i];
18437             commentList[firstMove+i] = NULL;
18438         }
18439
18440         storedGames++;
18441         forwardMostMove = firstMove; // truncate game so we can start variation
18442 }
18443
18444 void
18445 PushTail (int firstMove, int lastMove)
18446 {
18447         if(appData.icsActive) { // only in local mode
18448                 forwardMostMove = currentMove; // mimic old ICS behavior
18449                 return;
18450         }
18451         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18452
18453         PushInner(firstMove, lastMove);
18454         if(storedGames == 1) GreyRevert(FALSE);
18455         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18456 }
18457
18458 void
18459 PopInner (Boolean annotate)
18460 {
18461         int i, j, nrMoves;
18462         char buf[8000], moveBuf[20];
18463
18464         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18465         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18466         nrMoves = savedLast[storedGames] - currentMove;
18467         if(annotate) {
18468                 int cnt = 10;
18469                 if(!WhiteOnMove(currentMove))
18470                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18471                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18472                 for(i=currentMove; i<forwardMostMove; i++) {
18473                         if(WhiteOnMove(i))
18474                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18475                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18476                         strcat(buf, moveBuf);
18477                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18478                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18479                 }
18480                 strcat(buf, ")");
18481         }
18482         for(i=1; i<=nrMoves; i++) { // copy last variation back
18483             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18484             for(j=0; j<MOVE_LEN; j++)
18485                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18486             for(j=0; j<2*MOVE_LEN; j++)
18487                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18488             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18489             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18490             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18491             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18492             commentList[currentMove+i] = commentList[framePtr+i];
18493             commentList[framePtr+i] = NULL;
18494         }
18495         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18496         framePtr = savedFramePtr[storedGames];
18497         gameInfo.result = savedResult[storedGames];
18498         if(gameInfo.resultDetails != NULL) {
18499             free(gameInfo.resultDetails);
18500       }
18501         gameInfo.resultDetails = savedDetails[storedGames];
18502         forwardMostMove = currentMove + nrMoves;
18503 }
18504
18505 Boolean
18506 PopTail (Boolean annotate)
18507 {
18508         if(appData.icsActive) return FALSE; // only in local mode
18509         if(!storedGames) return FALSE; // sanity
18510         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18511
18512         PopInner(annotate);
18513         if(currentMove < forwardMostMove) ForwardEvent(); else
18514         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18515
18516         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18517         return TRUE;
18518 }
18519
18520 void
18521 CleanupTail ()
18522 {       // remove all shelved variations
18523         int i;
18524         for(i=0; i<storedGames; i++) {
18525             if(savedDetails[i])
18526                 free(savedDetails[i]);
18527             savedDetails[i] = NULL;
18528         }
18529         for(i=framePtr; i<MAX_MOVES; i++) {
18530                 if(commentList[i]) free(commentList[i]);
18531                 commentList[i] = NULL;
18532         }
18533         framePtr = MAX_MOVES-1;
18534         storedGames = 0;
18535 }
18536
18537 void
18538 LoadVariation (int index, char *text)
18539 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18540         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18541         int level = 0, move;
18542
18543         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18544         // first find outermost bracketing variation
18545         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18546             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18547                 if(*p == '{') wait = '}'; else
18548                 if(*p == '[') wait = ']'; else
18549                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18550                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18551             }
18552             if(*p == wait) wait = NULLCHAR; // closing ]} found
18553             p++;
18554         }
18555         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18556         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18557         end[1] = NULLCHAR; // clip off comment beyond variation
18558         ToNrEvent(currentMove-1);
18559         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18560         // kludge: use ParsePV() to append variation to game
18561         move = currentMove;
18562         ParsePV(start, TRUE, TRUE);
18563         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18564         ClearPremoveHighlights();
18565         CommentPopDown();
18566         ToNrEvent(currentMove+1);
18567 }
18568
18569 void
18570 LoadTheme ()
18571 {
18572     char *p, *q, buf[MSG_SIZ];
18573     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18574         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18575         ParseArgsFromString(buf);
18576         ActivateTheme(TRUE); // also redo colors
18577         return;
18578     }
18579     p = nickName;
18580     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18581     {
18582         int len;
18583         q = appData.themeNames;
18584         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18585       if(appData.useBitmaps) {
18586         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18587                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18588                 appData.liteBackTextureMode,
18589                 appData.darkBackTextureMode );
18590       } else {
18591         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18592                 Col2Text(2),   // lightSquareColor
18593                 Col2Text(3) ); // darkSquareColor
18594       }
18595       if(appData.useBorder) {
18596         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18597                 appData.border);
18598       } else {
18599         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18600       }
18601       if(appData.useFont) {
18602         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18603                 appData.renderPiecesWithFont,
18604                 appData.fontToPieceTable,
18605                 Col2Text(9),    // appData.fontBackColorWhite
18606                 Col2Text(10) ); // appData.fontForeColorBlack
18607       } else {
18608         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18609                 appData.pieceDirectory);
18610         if(!appData.pieceDirectory[0])
18611           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18612                 Col2Text(0),   // whitePieceColor
18613                 Col2Text(1) ); // blackPieceColor
18614       }
18615       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18616                 Col2Text(4),   // highlightSquareColor
18617                 Col2Text(5) ); // premoveHighlightColor
18618         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18619         if(insert != q) insert[-1] = NULLCHAR;
18620         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18621         if(q)   free(q);
18622     }
18623     ActivateTheme(FALSE);
18624 }