Change the piece order again
[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, 2015 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 SendToProgram P((char *message, ChessProgramState *cps));
196 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
197 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
198                            char *buf, int count, int error));
199 void SendTimeControl P((ChessProgramState *cps,
200                         int mps, long tc, int inc, int sd, int st));
201 char *TimeControlTagValue P((void));
202 void Attention P((ChessProgramState *cps));
203 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
204 int ResurrectChessProgram P((void));
205 void DisplayComment P((int moveNumber, char *text));
206 void DisplayMove P((int moveNumber));
207
208 void ParseGameHistory P((char *game));
209 void ParseBoard12 P((char *string));
210 void KeepAlive P((void));
211 void StartClocks P((void));
212 void SwitchClocks P((int nr));
213 void StopClocks P((void));
214 void ResetClocks P((void));
215 char *PGNDate P((void));
216 void SetGameInfo P((void));
217 int RegisterMove P((void));
218 void MakeRegisteredMove P((void));
219 void TruncateGame P((void));
220 int looking_at P((char *, int *, char *));
221 void CopyPlayerNameIntoFileName P((char **, char *));
222 char *SavePart P((char *));
223 int SaveGameOldStyle P((FILE *));
224 int SaveGamePGN P((FILE *));
225 int CheckFlags P((void));
226 long NextTickLength P((long));
227 void CheckTimeControl P((void));
228 void show_bytes P((FILE *, char *, int));
229 int string_to_rating P((char *str));
230 void ParseFeatures P((char* args, ChessProgramState *cps));
231 void InitBackEnd3 P((void));
232 void FeatureDone P((ChessProgramState* cps, int val));
233 void InitChessProgram P((ChessProgramState *cps, int setup));
234 void OutputKibitz(int window, char *text);
235 int PerpetualChase(int first, int last);
236 int EngineOutputIsUp();
237 void InitDrawingSizes(int x, int y);
238 void NextMatchGame P((void));
239 int NextTourneyGame P((int nr, int *swap));
240 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
241 FILE *WriteTourneyFile P((char *results, FILE *f));
242 void DisplayTwoMachinesTitle P(());
243 static void ExcludeClick P((int index));
244 void ToggleSecond P((void));
245 void PauseEngine P((ChessProgramState *cps));
246 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
247
248 #ifdef WIN32
249        extern void ConsoleCreate();
250 #endif
251
252 ChessProgramState *WhitePlayer();
253 int VerifyDisplayMode P(());
254
255 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
256 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
257 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
258 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
259 void ics_update_width P((int new_width));
260 extern char installDir[MSG_SIZ];
261 VariantClass startVariant; /* [HGM] nicks: initial variant */
262 Boolean abortMatch;
263
264 extern int tinyLayout, smallLayout;
265 ChessProgramStats programStats;
266 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
267 int endPV = -1;
268 static int exiting = 0; /* [HGM] moved to top */
269 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
270 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
271 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
272 int partnerHighlight[2];
273 Boolean partnerBoardValid = 0;
274 char partnerStatus[MSG_SIZ];
275 Boolean partnerUp;
276 Boolean originalFlip;
277 Boolean twoBoards = 0;
278 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
279 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
280 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
281 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
282 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
283 int opponentKibitzes;
284 int lastSavedGame; /* [HGM] save: ID of game */
285 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
286 extern int chatCount;
287 int chattingPartner;
288 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
289 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
290 char lastMsg[MSG_SIZ];
291 char lastTalker[MSG_SIZ];
292 ChessSquare pieceSweep = EmptySquare;
293 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
294 int promoDefaultAltered;
295 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
296 static int initPing = -1;
297 int border;       /* [HGM] width of board rim, needed to size seek graph  */
298 char bestMove[MSG_SIZ];
299 int solvingTime, totalTime;
300
301 /* States for ics_getting_history */
302 #define H_FALSE 0
303 #define H_REQUESTED 1
304 #define H_GOT_REQ_HEADER 2
305 #define H_GOT_UNREQ_HEADER 3
306 #define H_GETTING_MOVES 4
307 #define H_GOT_UNWANTED_HEADER 5
308
309 /* whosays values for GameEnds */
310 #define GE_ICS 0
311 #define GE_ENGINE 1
312 #define GE_PLAYER 2
313 #define GE_FILE 3
314 #define GE_XBOARD 4
315 #define GE_ENGINE1 5
316 #define GE_ENGINE2 6
317
318 /* Maximum number of games in a cmail message */
319 #define CMAIL_MAX_GAMES 20
320
321 /* Different types of move when calling RegisterMove */
322 #define CMAIL_MOVE   0
323 #define CMAIL_RESIGN 1
324 #define CMAIL_DRAW   2
325 #define CMAIL_ACCEPT 3
326
327 /* Different types of result to remember for each game */
328 #define CMAIL_NOT_RESULT 0
329 #define CMAIL_OLD_RESULT 1
330 #define CMAIL_NEW_RESULT 2
331
332 /* Telnet protocol constants */
333 #define TN_WILL 0373
334 #define TN_WONT 0374
335 #define TN_DO   0375
336 #define TN_DONT 0376
337 #define TN_IAC  0377
338 #define TN_ECHO 0001
339 #define TN_SGA  0003
340 #define TN_PORT 23
341
342 char*
343 safeStrCpy (char *dst, const char *src, size_t count)
344 { // [HGM] made safe
345   int i;
346   assert( dst != NULL );
347   assert( src != NULL );
348   assert( count > 0 );
349
350   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
351   if(  i == count && dst[count-1] != NULLCHAR)
352     {
353       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
354       if(appData.debugMode)
355         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
356     }
357
358   return dst;
359 }
360
361 /* Some compiler can't cast u64 to double
362  * This function do the job for us:
363
364  * We use the highest bit for cast, this only
365  * works if the highest bit is not
366  * in use (This should not happen)
367  *
368  * We used this for all compiler
369  */
370 double
371 u64ToDouble (u64 value)
372 {
373   double r;
374   u64 tmp = value & u64Const(0x7fffffffffffffff);
375   r = (double)(s64)tmp;
376   if (value & u64Const(0x8000000000000000))
377        r +=  9.2233720368547758080e18; /* 2^63 */
378  return r;
379 }
380
381 /* Fake up flags for now, as we aren't keeping track of castling
382    availability yet. [HGM] Change of logic: the flag now only
383    indicates the type of castlings allowed by the rule of the game.
384    The actual rights themselves are maintained in the array
385    castlingRights, as part of the game history, and are not probed
386    by this function.
387  */
388 int
389 PosFlags (index)
390 {
391   int flags = F_ALL_CASTLE_OK;
392   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
393   switch (gameInfo.variant) {
394   case VariantSuicide:
395     flags &= ~F_ALL_CASTLE_OK;
396   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
397     flags |= F_IGNORE_CHECK;
398   case VariantLosers:
399     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
400     break;
401   case VariantAtomic:
402     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
403     break;
404   case VariantKriegspiel:
405     flags |= F_KRIEGSPIEL_CAPTURE;
406     break;
407   case VariantCapaRandom:
408   case VariantFischeRandom:
409     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
410   case VariantNoCastle:
411   case VariantShatranj:
412   case VariantCourier:
413   case VariantMakruk:
414   case VariantASEAN:
415   case VariantGrand:
416     flags &= ~F_ALL_CASTLE_OK;
417     break;
418   case VariantChu:
419   case VariantChuChess:
420   case VariantLion:
421     flags |= F_NULL_MOVE;
422     break;
423   default:
424     break;
425   }
426   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
427   return flags;
428 }
429
430 FILE *gameFileFP, *debugFP, *serverFP;
431 char *currentDebugFile; // [HGM] debug split: to remember name
432
433 /*
434     [AS] Note: sometimes, the sscanf() function is used to parse the input
435     into a fixed-size buffer. Because of this, we must be prepared to
436     receive strings as long as the size of the input buffer, which is currently
437     set to 4K for Windows and 8K for the rest.
438     So, we must either allocate sufficiently large buffers here, or
439     reduce the size of the input buffer in the input reading part.
440 */
441
442 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
443 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
444 char thinkOutput1[MSG_SIZ*10];
445 char promoRestrict[MSG_SIZ];
446
447 ChessProgramState first, second, pairing;
448
449 /* premove variables */
450 int premoveToX = 0;
451 int premoveToY = 0;
452 int premoveFromX = 0;
453 int premoveFromY = 0;
454 int premovePromoChar = 0;
455 int gotPremove = 0;
456 Boolean alarmSounded;
457 /* end premove variables */
458
459 char *ics_prefix = "$";
460 enum ICS_TYPE ics_type = ICS_GENERIC;
461
462 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
463 int pauseExamForwardMostMove = 0;
464 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
465 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
466 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
467 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
468 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
469 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
470 int whiteFlag = FALSE, blackFlag = FALSE;
471 int userOfferedDraw = FALSE;
472 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
473 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
474 int cmailMoveType[CMAIL_MAX_GAMES];
475 long ics_clock_paused = 0;
476 ProcRef icsPR = NoProc, cmailPR = NoProc;
477 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
478 GameMode gameMode = BeginningOfGame;
479 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
480 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
481 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
482 int hiddenThinkOutputState = 0; /* [AS] */
483 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
484 int adjudicateLossPlies = 6;
485 char white_holding[64], black_holding[64];
486 TimeMark lastNodeCountTime;
487 long lastNodeCount=0;
488 int shiftKey, controlKey; // [HGM] set by mouse handler
489
490 int have_sent_ICS_logon = 0;
491 int movesPerSession;
492 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
493 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
494 Boolean adjustedClock;
495 long timeControl_2; /* [AS] Allow separate time controls */
496 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
497 long timeRemaining[2][MAX_MOVES];
498 int matchGame = 0, nextGame = 0, roundNr = 0;
499 Boolean waitingForGame = FALSE, startingEngine = FALSE;
500 TimeMark programStartTime, pauseStart;
501 char ics_handle[MSG_SIZ];
502 int have_set_title = 0;
503
504 /* animateTraining preserves the state of appData.animate
505  * when Training mode is activated. This allows the
506  * response to be animated when appData.animate == TRUE and
507  * appData.animateDragging == TRUE.
508  */
509 Boolean animateTraining;
510
511 GameInfo gameInfo;
512
513 AppData appData;
514
515 Board boards[MAX_MOVES];
516 /* [HGM] Following 7 needed for accurate legality tests: */
517 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
518 unsigned char initialRights[BOARD_FILES];
519 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
520 int   initialRulePlies, FENrulePlies;
521 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
522 int loadFlag = 0;
523 Boolean shuffleOpenings;
524 int mute; // mute all sounds
525
526 // [HGM] vari: next 12 to save and restore variations
527 #define MAX_VARIATIONS 10
528 int framePtr = MAX_MOVES-1; // points to free stack entry
529 int storedGames = 0;
530 int savedFirst[MAX_VARIATIONS];
531 int savedLast[MAX_VARIATIONS];
532 int savedFramePtr[MAX_VARIATIONS];
533 char *savedDetails[MAX_VARIATIONS];
534 ChessMove savedResult[MAX_VARIATIONS];
535
536 void PushTail P((int firstMove, int lastMove));
537 Boolean PopTail P((Boolean annotate));
538 void PushInner P((int firstMove, int lastMove));
539 void PopInner P((Boolean annotate));
540 void CleanupTail P((void));
541
542 ChessSquare  FIDEArray[2][BOARD_FILES] = {
543     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
546         BlackKing, BlackBishop, BlackKnight, BlackRook }
547 };
548
549 ChessSquare twoKingsArray[2][BOARD_FILES] = {
550     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
551         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
553         BlackKing, BlackKing, BlackKnight, BlackRook }
554 };
555
556 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
558         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
559     { BlackRook, BlackMan, BlackBishop, BlackQueen,
560         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
561 };
562
563 ChessSquare SpartanArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
565         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
566     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
567         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
568 };
569
570 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
571     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
572         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
573     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
574         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
575 };
576
577 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
578     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
579         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
580     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
581         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
582 };
583
584 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
585     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
586         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
587     { BlackRook, BlackKnight, BlackMan, BlackFerz,
588         BlackKing, BlackMan, BlackKnight, BlackRook }
589 };
590
591 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
592     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
593         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
594     { BlackRook, BlackKnight, BlackMan, BlackFerz,
595         BlackKing, BlackMan, BlackKnight, BlackRook }
596 };
597
598 ChessSquare  lionArray[2][BOARD_FILES] = {
599     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
600         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
601     { BlackRook, BlackLion, BlackBishop, BlackQueen,
602         BlackKing, BlackBishop, BlackKnight, BlackRook }
603 };
604
605
606 #if (BOARD_FILES>=10)
607 ChessSquare ShogiArray[2][BOARD_FILES] = {
608     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
609         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
610     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
611         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
612 };
613
614 ChessSquare XiangqiArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
616         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
618         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
619 };
620
621 ChessSquare CapablancaArray[2][BOARD_FILES] = {
622     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
623         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
624     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
625         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
626 };
627
628 ChessSquare GreatArray[2][BOARD_FILES] = {
629     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
630         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
631     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
632         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
633 };
634
635 ChessSquare JanusArray[2][BOARD_FILES] = {
636     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
637         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
638     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
639         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
640 };
641
642 ChessSquare GrandArray[2][BOARD_FILES] = {
643     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
644         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
645     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
646         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
647 };
648
649 ChessSquare ChuChessArray[2][BOARD_FILES] = {
650     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
651         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
652     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
653         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
654 };
655
656 #ifdef GOTHIC
657 ChessSquare GothicArray[2][BOARD_FILES] = {
658     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
659         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
660     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
661         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
662 };
663 #else // !GOTHIC
664 #define GothicArray CapablancaArray
665 #endif // !GOTHIC
666
667 #ifdef FALCON
668 ChessSquare FalconArray[2][BOARD_FILES] = {
669     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
670         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
671     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
672         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
673 };
674 #else // !FALCON
675 #define FalconArray CapablancaArray
676 #endif // !FALCON
677
678 #else // !(BOARD_FILES>=10)
679 #define XiangqiPosition FIDEArray
680 #define CapablancaArray FIDEArray
681 #define GothicArray FIDEArray
682 #define GreatArray FIDEArray
683 #endif // !(BOARD_FILES>=10)
684
685 #if (BOARD_FILES>=12)
686 ChessSquare CourierArray[2][BOARD_FILES] = {
687     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
688         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
689     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
690         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
691 };
692 ChessSquare ChuArray[6][BOARD_FILES] = {
693     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
694       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
695     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
696       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
697     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
698       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
699     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
700       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
701     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
702       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
703     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
704       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
705 };
706 #else // !(BOARD_FILES>=12)
707 #define CourierArray CapablancaArray
708 #define ChuArray CapablancaArray
709 #endif // !(BOARD_FILES>=12)
710
711
712 Board initialPosition;
713
714
715 /* Convert str to a rating. Checks for special cases of "----",
716
717    "++++", etc. Also strips ()'s */
718 int
719 string_to_rating (char *str)
720 {
721   while(*str && !isdigit(*str)) ++str;
722   if (!*str)
723     return 0;   /* One of the special "no rating" cases */
724   else
725     return atoi(str);
726 }
727
728 void
729 ClearProgramStats ()
730 {
731     /* Init programStats */
732     programStats.movelist[0] = 0;
733     programStats.depth = 0;
734     programStats.nr_moves = 0;
735     programStats.moves_left = 0;
736     programStats.nodes = 0;
737     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
738     programStats.score = 0;
739     programStats.got_only_move = 0;
740     programStats.got_fail = 0;
741     programStats.line_is_book = 0;
742 }
743
744 void
745 CommonEngineInit ()
746 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
747     if (appData.firstPlaysBlack) {
748         first.twoMachinesColor = "black\n";
749         second.twoMachinesColor = "white\n";
750     } else {
751         first.twoMachinesColor = "white\n";
752         second.twoMachinesColor = "black\n";
753     }
754
755     first.other = &second;
756     second.other = &first;
757
758     { float norm = 1;
759         if(appData.timeOddsMode) {
760             norm = appData.timeOdds[0];
761             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
762         }
763         first.timeOdds  = appData.timeOdds[0]/norm;
764         second.timeOdds = appData.timeOdds[1]/norm;
765     }
766
767     if(programVersion) free(programVersion);
768     if (appData.noChessProgram) {
769         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
770         sprintf(programVersion, "%s", PACKAGE_STRING);
771     } else {
772       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
773       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
774       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
775     }
776 }
777
778 void
779 UnloadEngine (ChessProgramState *cps)
780 {
781         /* Kill off first chess program */
782         if (cps->isr != NULL)
783           RemoveInputSource(cps->isr);
784         cps->isr = NULL;
785
786         if (cps->pr != NoProc) {
787             ExitAnalyzeMode();
788             DoSleep( appData.delayBeforeQuit );
789             SendToProgram("quit\n", cps);
790             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
791         }
792         cps->pr = NoProc;
793         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
794 }
795
796 void
797 ClearOptions (ChessProgramState *cps)
798 {
799     int i;
800     cps->nrOptions = cps->comboCnt = 0;
801     for(i=0; i<MAX_OPTIONS; i++) {
802         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
803         cps->option[i].textValue = 0;
804     }
805 }
806
807 char *engineNames[] = {
808   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
809      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
810 N_("first"),
811   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
812      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
813 N_("second")
814 };
815
816 void
817 InitEngine (ChessProgramState *cps, int n)
818 {   // [HGM] all engine initialiation put in a function that does one engine
819
820     ClearOptions(cps);
821
822     cps->which = engineNames[n];
823     cps->maybeThinking = FALSE;
824     cps->pr = NoProc;
825     cps->isr = NULL;
826     cps->sendTime = 2;
827     cps->sendDrawOffers = 1;
828
829     cps->program = appData.chessProgram[n];
830     cps->host = appData.host[n];
831     cps->dir = appData.directory[n];
832     cps->initString = appData.engInitString[n];
833     cps->computerString = appData.computerString[n];
834     cps->useSigint  = TRUE;
835     cps->useSigterm = TRUE;
836     cps->reuse = appData.reuse[n];
837     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
838     cps->useSetboard = FALSE;
839     cps->useSAN = FALSE;
840     cps->usePing = FALSE;
841     cps->lastPing = 0;
842     cps->lastPong = 0;
843     cps->usePlayother = FALSE;
844     cps->useColors = TRUE;
845     cps->useUsermove = FALSE;
846     cps->sendICS = FALSE;
847     cps->sendName = appData.icsActive;
848     cps->sdKludge = FALSE;
849     cps->stKludge = FALSE;
850     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
851     TidyProgramName(cps->program, cps->host, cps->tidy);
852     cps->matchWins = 0;
853     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
854     cps->analysisSupport = 2; /* detect */
855     cps->analyzing = FALSE;
856     cps->initDone = FALSE;
857     cps->reload = FALSE;
858     cps->pseudo = appData.pseudo[n];
859
860     /* New features added by Tord: */
861     cps->useFEN960 = FALSE;
862     cps->useOOCastle = TRUE;
863     /* End of new features added by Tord. */
864     cps->fenOverride  = appData.fenOverride[n];
865
866     /* [HGM] time odds: set factor for each machine */
867     cps->timeOdds  = appData.timeOdds[n];
868
869     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
870     cps->accumulateTC = appData.accumulateTC[n];
871     cps->maxNrOfSessions = 1;
872
873     /* [HGM] debug */
874     cps->debug = FALSE;
875
876     cps->drawDepth = appData.drawDepth[n];
877     cps->supportsNPS = UNKNOWN;
878     cps->memSize = FALSE;
879     cps->maxCores = FALSE;
880     ASSIGN(cps->egtFormats, "");
881
882     /* [HGM] options */
883     cps->optionSettings  = appData.engOptions[n];
884
885     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
886     cps->isUCI = appData.isUCI[n]; /* [AS] */
887     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
888     cps->highlight = 0;
889
890     if (appData.protocolVersion[n] > PROTOVER
891         || appData.protocolVersion[n] < 1)
892       {
893         char buf[MSG_SIZ];
894         int len;
895
896         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
897                        appData.protocolVersion[n]);
898         if( (len >= MSG_SIZ) && appData.debugMode )
899           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
900
901         DisplayFatalError(buf, 0, 2);
902       }
903     else
904       {
905         cps->protocolVersion = appData.protocolVersion[n];
906       }
907
908     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
909     ParseFeatures(appData.featureDefaults, cps);
910 }
911
912 ChessProgramState *savCps;
913
914 GameMode oldMode;
915
916 void
917 LoadEngine ()
918 {
919     int i;
920     if(WaitForEngine(savCps, LoadEngine)) return;
921     CommonEngineInit(); // recalculate time odds
922     if(gameInfo.variant != StringToVariant(appData.variant)) {
923         // we changed variant when loading the engine; this forces us to reset
924         Reset(TRUE, savCps != &first);
925         oldMode = BeginningOfGame; // to prevent restoring old mode
926     }
927     InitChessProgram(savCps, FALSE);
928     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
929     DisplayMessage("", "");
930     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
931     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
932     ThawUI();
933     SetGNUMode();
934     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
935 }
936
937 void
938 ReplaceEngine (ChessProgramState *cps, int n)
939 {
940     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
941     keepInfo = 1;
942     if(oldMode != BeginningOfGame) EditGameEvent();
943     keepInfo = 0;
944     UnloadEngine(cps);
945     appData.noChessProgram = FALSE;
946     appData.clockMode = TRUE;
947     InitEngine(cps, n);
948     UpdateLogos(TRUE);
949     if(n) return; // only startup first engine immediately; second can wait
950     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
951     LoadEngine();
952 }
953
954 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
955 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
956
957 static char resetOptions[] =
958         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
959         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
960         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
961         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
962
963 void
964 FloatToFront(char **list, char *engineLine)
965 {
966     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
967     int i=0;
968     if(appData.recentEngines <= 0) return;
969     TidyProgramName(engineLine, "localhost", tidy+1);
970     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
971     strncpy(buf+1, *list, MSG_SIZ-50);
972     if(p = strstr(buf, tidy)) { // tidy name appears in list
973         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
974         while(*p++ = *++q); // squeeze out
975     }
976     strcat(tidy, buf+1); // put list behind tidy name
977     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
978     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
979     ASSIGN(*list, tidy+1);
980 }
981
982 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
983
984 void
985 Load (ChessProgramState *cps, int i)
986 {
987     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
988     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
989         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
990         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
991         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
992         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
993         appData.firstProtocolVersion = PROTOVER;
994         ParseArgsFromString(buf);
995         SwapEngines(i);
996         ReplaceEngine(cps, i);
997         FloatToFront(&appData.recentEngineList, engineLine);
998         return;
999     }
1000     p = engineName;
1001     while(q = strchr(p, SLASH)) p = q+1;
1002     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1003     if(engineDir[0] != NULLCHAR) {
1004         ASSIGN(appData.directory[i], engineDir); p = engineName;
1005     } else if(p != engineName) { // derive directory from engine path, when not given
1006         p[-1] = 0;
1007         ASSIGN(appData.directory[i], engineName);
1008         p[-1] = SLASH;
1009         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1010     } else { ASSIGN(appData.directory[i], "."); }
1011     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1012     if(params[0]) {
1013         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1014         snprintf(command, MSG_SIZ, "%s %s", p, params);
1015         p = command;
1016     }
1017     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1018     ASSIGN(appData.chessProgram[i], p);
1019     appData.isUCI[i] = isUCI;
1020     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1021     appData.hasOwnBookUCI[i] = hasBook;
1022     if(!nickName[0]) useNick = FALSE;
1023     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1024     if(addToList) {
1025         int len;
1026         char quote;
1027         q = firstChessProgramNames;
1028         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1029         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1030         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1031                         quote, p, quote, appData.directory[i],
1032                         useNick ? " -fn \"" : "",
1033                         useNick ? nickName : "",
1034                         useNick ? "\"" : "",
1035                         v1 ? " -firstProtocolVersion 1" : "",
1036                         hasBook ? "" : " -fNoOwnBookUCI",
1037                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1038                         storeVariant ? " -variant " : "",
1039                         storeVariant ? VariantName(gameInfo.variant) : "");
1040         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1041         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1042         if(insert != q) insert[-1] = NULLCHAR;
1043         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1044         if(q)   free(q);
1045         FloatToFront(&appData.recentEngineList, buf);
1046     }
1047     ReplaceEngine(cps, i);
1048 }
1049
1050 void
1051 InitTimeControls ()
1052 {
1053     int matched, min, sec;
1054     /*
1055      * Parse timeControl resource
1056      */
1057     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1058                           appData.movesPerSession)) {
1059         char buf[MSG_SIZ];
1060         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1061         DisplayFatalError(buf, 0, 2);
1062     }
1063
1064     /*
1065      * Parse searchTime resource
1066      */
1067     if (*appData.searchTime != NULLCHAR) {
1068         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1069         if (matched == 1) {
1070             searchTime = min * 60;
1071         } else if (matched == 2) {
1072             searchTime = min * 60 + sec;
1073         } else {
1074             char buf[MSG_SIZ];
1075             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1076             DisplayFatalError(buf, 0, 2);
1077         }
1078     }
1079 }
1080
1081 void
1082 InitBackEnd1 ()
1083 {
1084
1085     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1086     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1087
1088     GetTimeMark(&programStartTime);
1089     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1090     appData.seedBase = random() + (random()<<15);
1091     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1092
1093     ClearProgramStats();
1094     programStats.ok_to_send = 1;
1095     programStats.seen_stat = 0;
1096
1097     /*
1098      * Initialize game list
1099      */
1100     ListNew(&gameList);
1101
1102
1103     /*
1104      * Internet chess server status
1105      */
1106     if (appData.icsActive) {
1107         appData.matchMode = FALSE;
1108         appData.matchGames = 0;
1109 #if ZIPPY
1110         appData.noChessProgram = !appData.zippyPlay;
1111 #else
1112         appData.zippyPlay = FALSE;
1113         appData.zippyTalk = FALSE;
1114         appData.noChessProgram = TRUE;
1115 #endif
1116         if (*appData.icsHelper != NULLCHAR) {
1117             appData.useTelnet = TRUE;
1118             appData.telnetProgram = appData.icsHelper;
1119         }
1120     } else {
1121         appData.zippyTalk = appData.zippyPlay = FALSE;
1122     }
1123
1124     /* [AS] Initialize pv info list [HGM] and game state */
1125     {
1126         int i, j;
1127
1128         for( i=0; i<=framePtr; i++ ) {
1129             pvInfoList[i].depth = -1;
1130             boards[i][EP_STATUS] = EP_NONE;
1131             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1132         }
1133     }
1134
1135     InitTimeControls();
1136
1137     /* [AS] Adjudication threshold */
1138     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1139
1140     InitEngine(&first, 0);
1141     InitEngine(&second, 1);
1142     CommonEngineInit();
1143
1144     pairing.which = "pairing"; // pairing engine
1145     pairing.pr = NoProc;
1146     pairing.isr = NULL;
1147     pairing.program = appData.pairingEngine;
1148     pairing.host = "localhost";
1149     pairing.dir = ".";
1150
1151     if (appData.icsActive) {
1152         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1153     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1154         appData.clockMode = FALSE;
1155         first.sendTime = second.sendTime = 0;
1156     }
1157
1158 #if ZIPPY
1159     /* Override some settings from environment variables, for backward
1160        compatibility.  Unfortunately it's not feasible to have the env
1161        vars just set defaults, at least in xboard.  Ugh.
1162     */
1163     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1164       ZippyInit();
1165     }
1166 #endif
1167
1168     if (!appData.icsActive) {
1169       char buf[MSG_SIZ];
1170       int len;
1171
1172       /* Check for variants that are supported only in ICS mode,
1173          or not at all.  Some that are accepted here nevertheless
1174          have bugs; see comments below.
1175       */
1176       VariantClass variant = StringToVariant(appData.variant);
1177       switch (variant) {
1178       case VariantBughouse:     /* need four players and two boards */
1179       case VariantKriegspiel:   /* need to hide pieces and move details */
1180         /* case VariantFischeRandom: (Fabien: moved below) */
1181         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1182         if( (len >= MSG_SIZ) && appData.debugMode )
1183           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1184
1185         DisplayFatalError(buf, 0, 2);
1186         return;
1187
1188       case VariantUnknown:
1189       case VariantLoadable:
1190       case Variant29:
1191       case Variant30:
1192       case Variant31:
1193       case Variant32:
1194       case Variant33:
1195       case Variant34:
1196       case Variant35:
1197       case Variant36:
1198       default:
1199         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1200         if( (len >= MSG_SIZ) && appData.debugMode )
1201           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1202
1203         DisplayFatalError(buf, 0, 2);
1204         return;
1205
1206       case VariantNormal:     /* definitely works! */
1207         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1208           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1209           return;
1210         }
1211       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1212       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1213       case VariantGothic:     /* [HGM] should work */
1214       case VariantCapablanca: /* [HGM] should work */
1215       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1216       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1217       case VariantChu:        /* [HGM] experimental */
1218       case VariantKnightmate: /* [HGM] should work */
1219       case VariantCylinder:   /* [HGM] untested */
1220       case VariantFalcon:     /* [HGM] untested */
1221       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1222                                  offboard interposition not understood */
1223       case VariantWildCastle: /* pieces not automatically shuffled */
1224       case VariantNoCastle:   /* pieces not automatically shuffled */
1225       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1226       case VariantLosers:     /* should work except for win condition,
1227                                  and doesn't know captures are mandatory */
1228       case VariantSuicide:    /* should work except for win condition,
1229                                  and doesn't know captures are mandatory */
1230       case VariantGiveaway:   /* should work except for win condition,
1231                                  and doesn't know captures are mandatory */
1232       case VariantTwoKings:   /* should work */
1233       case VariantAtomic:     /* should work except for win condition */
1234       case Variant3Check:     /* should work except for win condition */
1235       case VariantShatranj:   /* should work except for all win conditions */
1236       case VariantMakruk:     /* should work except for draw countdown */
1237       case VariantASEAN :     /* should work except for draw countdown */
1238       case VariantBerolina:   /* might work if TestLegality is off */
1239       case VariantCapaRandom: /* should work */
1240       case VariantJanus:      /* should work */
1241       case VariantSuper:      /* experimental */
1242       case VariantGreat:      /* experimental, requires legality testing to be off */
1243       case VariantSChess:     /* S-Chess, should work */
1244       case VariantGrand:      /* should work */
1245       case VariantSpartan:    /* should work */
1246       case VariantLion:       /* should work */
1247       case VariantChuChess:   /* should work */
1248         break;
1249       }
1250     }
1251
1252 }
1253
1254 int
1255 NextIntegerFromString (char ** str, long * value)
1256 {
1257     int result = -1;
1258     char * s = *str;
1259
1260     while( *s == ' ' || *s == '\t' ) {
1261         s++;
1262     }
1263
1264     *value = 0;
1265
1266     if( *s >= '0' && *s <= '9' ) {
1267         while( *s >= '0' && *s <= '9' ) {
1268             *value = *value * 10 + (*s - '0');
1269             s++;
1270         }
1271
1272         result = 0;
1273     }
1274
1275     *str = s;
1276
1277     return result;
1278 }
1279
1280 int
1281 NextTimeControlFromString (char ** str, long * value)
1282 {
1283     long temp;
1284     int result = NextIntegerFromString( str, &temp );
1285
1286     if( result == 0 ) {
1287         *value = temp * 60; /* Minutes */
1288         if( **str == ':' ) {
1289             (*str)++;
1290             result = NextIntegerFromString( str, &temp );
1291             *value += temp; /* Seconds */
1292         }
1293     }
1294
1295     return result;
1296 }
1297
1298 int
1299 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1300 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1301     int result = -1, type = 0; long temp, temp2;
1302
1303     if(**str != ':') return -1; // old params remain in force!
1304     (*str)++;
1305     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1306     if( NextIntegerFromString( str, &temp ) ) return -1;
1307     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1308
1309     if(**str != '/') {
1310         /* time only: incremental or sudden-death time control */
1311         if(**str == '+') { /* increment follows; read it */
1312             (*str)++;
1313             if(**str == '!') type = *(*str)++; // Bronstein TC
1314             if(result = NextIntegerFromString( str, &temp2)) return -1;
1315             *inc = temp2 * 1000;
1316             if(**str == '.') { // read fraction of increment
1317                 char *start = ++(*str);
1318                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1319                 temp2 *= 1000;
1320                 while(start++ < *str) temp2 /= 10;
1321                 *inc += temp2;
1322             }
1323         } else *inc = 0;
1324         *moves = 0; *tc = temp * 1000; *incType = type;
1325         return 0;
1326     }
1327
1328     (*str)++; /* classical time control */
1329     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1330
1331     if(result == 0) {
1332         *moves = temp;
1333         *tc    = temp2 * 1000;
1334         *inc   = 0;
1335         *incType = type;
1336     }
1337     return result;
1338 }
1339
1340 int
1341 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1342 {   /* [HGM] get time to add from the multi-session time-control string */
1343     int incType, moves=1; /* kludge to force reading of first session */
1344     long time, increment;
1345     char *s = tcString;
1346
1347     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1348     do {
1349         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1350         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1351         if(movenr == -1) return time;    /* last move before new session     */
1352         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1353         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1354         if(!moves) return increment;     /* current session is incremental   */
1355         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1356     } while(movenr >= -1);               /* try again for next session       */
1357
1358     return 0; // no new time quota on this move
1359 }
1360
1361 int
1362 ParseTimeControl (char *tc, float ti, int mps)
1363 {
1364   long tc1;
1365   long tc2;
1366   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1367   int min, sec=0;
1368
1369   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1370   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1371       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1372   if(ti > 0) {
1373
1374     if(mps)
1375       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1376     else
1377       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1378   } else {
1379     if(mps)
1380       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1381     else
1382       snprintf(buf, MSG_SIZ, ":%s", mytc);
1383   }
1384   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1385
1386   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1387     return FALSE;
1388   }
1389
1390   if( *tc == '/' ) {
1391     /* Parse second time control */
1392     tc++;
1393
1394     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1395       return FALSE;
1396     }
1397
1398     if( tc2 == 0 ) {
1399       return FALSE;
1400     }
1401
1402     timeControl_2 = tc2 * 1000;
1403   }
1404   else {
1405     timeControl_2 = 0;
1406   }
1407
1408   if( tc1 == 0 ) {
1409     return FALSE;
1410   }
1411
1412   timeControl = tc1 * 1000;
1413
1414   if (ti >= 0) {
1415     timeIncrement = ti * 1000;  /* convert to ms */
1416     movesPerSession = 0;
1417   } else {
1418     timeIncrement = 0;
1419     movesPerSession = mps;
1420   }
1421   return TRUE;
1422 }
1423
1424 void
1425 InitBackEnd2 ()
1426 {
1427     if (appData.debugMode) {
1428 #    ifdef __GIT_VERSION
1429       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1430 #    else
1431       fprintf(debugFP, "Version: %s\n", programVersion);
1432 #    endif
1433     }
1434     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1435
1436     set_cont_sequence(appData.wrapContSeq);
1437     if (appData.matchGames > 0) {
1438         appData.matchMode = TRUE;
1439     } else if (appData.matchMode) {
1440         appData.matchGames = 1;
1441     }
1442     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1443         appData.matchGames = appData.sameColorGames;
1444     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1445         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1446         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1447     }
1448     Reset(TRUE, FALSE);
1449     if (appData.noChessProgram || first.protocolVersion == 1) {
1450       InitBackEnd3();
1451     } else {
1452       /* kludge: allow timeout for initial "feature" commands */
1453       FreezeUI();
1454       DisplayMessage("", _("Starting chess program"));
1455       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1456     }
1457 }
1458
1459 int
1460 CalculateIndex (int index, int gameNr)
1461 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1462     int res;
1463     if(index > 0) return index; // fixed nmber
1464     if(index == 0) return 1;
1465     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1466     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1467     return res;
1468 }
1469
1470 int
1471 LoadGameOrPosition (int gameNr)
1472 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1473     if (*appData.loadGameFile != NULLCHAR) {
1474         if (!LoadGameFromFile(appData.loadGameFile,
1475                 CalculateIndex(appData.loadGameIndex, gameNr),
1476                               appData.loadGameFile, FALSE)) {
1477             DisplayFatalError(_("Bad game file"), 0, 1);
1478             return 0;
1479         }
1480     } else if (*appData.loadPositionFile != NULLCHAR) {
1481         if (!LoadPositionFromFile(appData.loadPositionFile,
1482                 CalculateIndex(appData.loadPositionIndex, gameNr),
1483                                   appData.loadPositionFile)) {
1484             DisplayFatalError(_("Bad position file"), 0, 1);
1485             return 0;
1486         }
1487     }
1488     return 1;
1489 }
1490
1491 void
1492 ReserveGame (int gameNr, char resChar)
1493 {
1494     FILE *tf = fopen(appData.tourneyFile, "r+");
1495     char *p, *q, c, buf[MSG_SIZ];
1496     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1497     safeStrCpy(buf, lastMsg, MSG_SIZ);
1498     DisplayMessage(_("Pick new game"), "");
1499     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1500     ParseArgsFromFile(tf);
1501     p = q = appData.results;
1502     if(appData.debugMode) {
1503       char *r = appData.participants;
1504       fprintf(debugFP, "results = '%s'\n", p);
1505       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1506       fprintf(debugFP, "\n");
1507     }
1508     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1509     nextGame = q - p;
1510     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1511     safeStrCpy(q, p, strlen(p) + 2);
1512     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1513     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1514     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1515         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1516         q[nextGame] = '*';
1517     }
1518     fseek(tf, -(strlen(p)+4), SEEK_END);
1519     c = fgetc(tf);
1520     if(c != '"') // depending on DOS or Unix line endings we can be one off
1521          fseek(tf, -(strlen(p)+2), SEEK_END);
1522     else fseek(tf, -(strlen(p)+3), SEEK_END);
1523     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1524     DisplayMessage(buf, "");
1525     free(p); appData.results = q;
1526     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1527        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1528       int round = appData.defaultMatchGames * appData.tourneyType;
1529       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1530          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1531         UnloadEngine(&first);  // next game belongs to other pairing;
1532         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1533     }
1534     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1535 }
1536
1537 void
1538 MatchEvent (int mode)
1539 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1540         int dummy;
1541         if(matchMode) { // already in match mode: switch it off
1542             abortMatch = TRUE;
1543             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1544             return;
1545         }
1546 //      if(gameMode != BeginningOfGame) {
1547 //          DisplayError(_("You can only start a match from the initial position."), 0);
1548 //          return;
1549 //      }
1550         abortMatch = FALSE;
1551         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1552         /* Set up machine vs. machine match */
1553         nextGame = 0;
1554         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1555         if(appData.tourneyFile[0]) {
1556             ReserveGame(-1, 0);
1557             if(nextGame > appData.matchGames) {
1558                 char buf[MSG_SIZ];
1559                 if(strchr(appData.results, '*') == NULL) {
1560                     FILE *f;
1561                     appData.tourneyCycles++;
1562                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1563                         fclose(f);
1564                         NextTourneyGame(-1, &dummy);
1565                         ReserveGame(-1, 0);
1566                         if(nextGame <= appData.matchGames) {
1567                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1568                             matchMode = mode;
1569                             ScheduleDelayedEvent(NextMatchGame, 10000);
1570                             return;
1571                         }
1572                     }
1573                 }
1574                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1575                 DisplayError(buf, 0);
1576                 appData.tourneyFile[0] = 0;
1577                 return;
1578             }
1579         } else
1580         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1581             DisplayFatalError(_("Can't have a match with no chess programs"),
1582                               0, 2);
1583             return;
1584         }
1585         matchMode = mode;
1586         matchGame = roundNr = 1;
1587         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1588         NextMatchGame();
1589 }
1590
1591 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1592
1593 void
1594 InitBackEnd3 P((void))
1595 {
1596     GameMode initialMode;
1597     char buf[MSG_SIZ];
1598     int err, len;
1599
1600     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1601        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1602         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1603        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1604        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1605         char c, *q = first.variants, *p = strchr(q, ',');
1606         if(p) *p = NULLCHAR;
1607         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1608             int w, h, s;
1609             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1610                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1611             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1612             Reset(TRUE, FALSE);         // and re-initialize
1613         }
1614         if(p) *p = ',';
1615     }
1616
1617     InitChessProgram(&first, startedFromSetupPosition);
1618
1619     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1620         free(programVersion);
1621         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1622         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1623         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1624     }
1625
1626     if (appData.icsActive) {
1627 #ifdef WIN32
1628         /* [DM] Make a console window if needed [HGM] merged ifs */
1629         ConsoleCreate();
1630 #endif
1631         err = establish();
1632         if (err != 0)
1633           {
1634             if (*appData.icsCommPort != NULLCHAR)
1635               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1636                              appData.icsCommPort);
1637             else
1638               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1639                         appData.icsHost, appData.icsPort);
1640
1641             if( (len >= MSG_SIZ) && appData.debugMode )
1642               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1643
1644             DisplayFatalError(buf, err, 1);
1645             return;
1646         }
1647         SetICSMode();
1648         telnetISR =
1649           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1650         fromUserISR =
1651           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1652         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1653             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1654     } else if (appData.noChessProgram) {
1655         SetNCPMode();
1656     } else {
1657         SetGNUMode();
1658     }
1659
1660     if (*appData.cmailGameName != NULLCHAR) {
1661         SetCmailMode();
1662         OpenLoopback(&cmailPR);
1663         cmailISR =
1664           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1665     }
1666
1667     ThawUI();
1668     DisplayMessage("", "");
1669     if (StrCaseCmp(appData.initialMode, "") == 0) {
1670       initialMode = BeginningOfGame;
1671       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1672         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1673         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1674         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1675         ModeHighlight();
1676       }
1677     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1678       initialMode = TwoMachinesPlay;
1679     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1680       initialMode = AnalyzeFile;
1681     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1682       initialMode = AnalyzeMode;
1683     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1684       initialMode = MachinePlaysWhite;
1685     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1686       initialMode = MachinePlaysBlack;
1687     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1688       initialMode = EditGame;
1689     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1690       initialMode = EditPosition;
1691     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1692       initialMode = Training;
1693     } else {
1694       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1695       if( (len >= MSG_SIZ) && appData.debugMode )
1696         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1697
1698       DisplayFatalError(buf, 0, 2);
1699       return;
1700     }
1701
1702     if (appData.matchMode) {
1703         if(appData.tourneyFile[0]) { // start tourney from command line
1704             FILE *f;
1705             if(f = fopen(appData.tourneyFile, "r")) {
1706                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1707                 fclose(f);
1708                 appData.clockMode = TRUE;
1709                 SetGNUMode();
1710             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1711         }
1712         MatchEvent(TRUE);
1713     } else if (*appData.cmailGameName != NULLCHAR) {
1714         /* Set up cmail mode */
1715         ReloadCmailMsgEvent(TRUE);
1716     } else {
1717         /* Set up other modes */
1718         if (initialMode == AnalyzeFile) {
1719           if (*appData.loadGameFile == NULLCHAR) {
1720             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1721             return;
1722           }
1723         }
1724         if (*appData.loadGameFile != NULLCHAR) {
1725             (void) LoadGameFromFile(appData.loadGameFile,
1726                                     appData.loadGameIndex,
1727                                     appData.loadGameFile, TRUE);
1728         } else if (*appData.loadPositionFile != NULLCHAR) {
1729             (void) LoadPositionFromFile(appData.loadPositionFile,
1730                                         appData.loadPositionIndex,
1731                                         appData.loadPositionFile);
1732             /* [HGM] try to make self-starting even after FEN load */
1733             /* to allow automatic setup of fairy variants with wtm */
1734             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1735                 gameMode = BeginningOfGame;
1736                 setboardSpoiledMachineBlack = 1;
1737             }
1738             /* [HGM] loadPos: make that every new game uses the setup */
1739             /* from file as long as we do not switch variant          */
1740             if(!blackPlaysFirst) {
1741                 startedFromPositionFile = TRUE;
1742                 CopyBoard(filePosition, boards[0]);
1743                 CopyBoard(initialPosition, boards[0]);
1744             }
1745         }
1746         if (initialMode == AnalyzeMode) {
1747           if (appData.noChessProgram) {
1748             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1749             return;
1750           }
1751           if (appData.icsActive) {
1752             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1753             return;
1754           }
1755           AnalyzeModeEvent();
1756         } else if (initialMode == AnalyzeFile) {
1757           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1758           ShowThinkingEvent();
1759           AnalyzeFileEvent();
1760           AnalysisPeriodicEvent(1);
1761         } else if (initialMode == MachinePlaysWhite) {
1762           if (appData.noChessProgram) {
1763             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1764                               0, 2);
1765             return;
1766           }
1767           if (appData.icsActive) {
1768             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1769                               0, 2);
1770             return;
1771           }
1772           MachineWhiteEvent();
1773         } else if (initialMode == MachinePlaysBlack) {
1774           if (appData.noChessProgram) {
1775             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1776                               0, 2);
1777             return;
1778           }
1779           if (appData.icsActive) {
1780             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1781                               0, 2);
1782             return;
1783           }
1784           MachineBlackEvent();
1785         } else if (initialMode == TwoMachinesPlay) {
1786           if (appData.noChessProgram) {
1787             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1788                               0, 2);
1789             return;
1790           }
1791           if (appData.icsActive) {
1792             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1793                               0, 2);
1794             return;
1795           }
1796           TwoMachinesEvent();
1797         } else if (initialMode == EditGame) {
1798           EditGameEvent();
1799         } else if (initialMode == EditPosition) {
1800           EditPositionEvent();
1801         } else if (initialMode == Training) {
1802           if (*appData.loadGameFile == NULLCHAR) {
1803             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1804             return;
1805           }
1806           TrainingEvent();
1807         }
1808     }
1809 }
1810
1811 void
1812 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1813 {
1814     DisplayBook(current+1);
1815
1816     MoveHistorySet( movelist, first, last, current, pvInfoList );
1817
1818     EvalGraphSet( first, last, current, pvInfoList );
1819
1820     MakeEngineOutputTitle();
1821 }
1822
1823 /*
1824  * Establish will establish a contact to a remote host.port.
1825  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1826  *  used to talk to the host.
1827  * Returns 0 if okay, error code if not.
1828  */
1829 int
1830 establish ()
1831 {
1832     char buf[MSG_SIZ];
1833
1834     if (*appData.icsCommPort != NULLCHAR) {
1835         /* Talk to the host through a serial comm port */
1836         return OpenCommPort(appData.icsCommPort, &icsPR);
1837
1838     } else if (*appData.gateway != NULLCHAR) {
1839         if (*appData.remoteShell == NULLCHAR) {
1840             /* Use the rcmd protocol to run telnet program on a gateway host */
1841             snprintf(buf, sizeof(buf), "%s %s %s",
1842                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1843             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1844
1845         } else {
1846             /* Use the rsh program to run telnet program on a gateway host */
1847             if (*appData.remoteUser == NULLCHAR) {
1848                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1849                         appData.gateway, appData.telnetProgram,
1850                         appData.icsHost, appData.icsPort);
1851             } else {
1852                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1853                         appData.remoteShell, appData.gateway,
1854                         appData.remoteUser, appData.telnetProgram,
1855                         appData.icsHost, appData.icsPort);
1856             }
1857             return StartChildProcess(buf, "", &icsPR);
1858
1859         }
1860     } else if (appData.useTelnet) {
1861         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1862
1863     } else {
1864         /* TCP socket interface differs somewhat between
1865            Unix and NT; handle details in the front end.
1866            */
1867         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1868     }
1869 }
1870
1871 void
1872 EscapeExpand (char *p, char *q)
1873 {       // [HGM] initstring: routine to shape up string arguments
1874         while(*p++ = *q++) if(p[-1] == '\\')
1875             switch(*q++) {
1876                 case 'n': p[-1] = '\n'; break;
1877                 case 'r': p[-1] = '\r'; break;
1878                 case 't': p[-1] = '\t'; break;
1879                 case '\\': p[-1] = '\\'; break;
1880                 case 0: *p = 0; return;
1881                 default: p[-1] = q[-1]; break;
1882             }
1883 }
1884
1885 void
1886 show_bytes (FILE *fp, char *buf, int count)
1887 {
1888     while (count--) {
1889         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1890             fprintf(fp, "\\%03o", *buf & 0xff);
1891         } else {
1892             putc(*buf, fp);
1893         }
1894         buf++;
1895     }
1896     fflush(fp);
1897 }
1898
1899 /* Returns an errno value */
1900 int
1901 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1902 {
1903     char buf[8192], *p, *q, *buflim;
1904     int left, newcount, outcount;
1905
1906     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1907         *appData.gateway != NULLCHAR) {
1908         if (appData.debugMode) {
1909             fprintf(debugFP, ">ICS: ");
1910             show_bytes(debugFP, message, count);
1911             fprintf(debugFP, "\n");
1912         }
1913         return OutputToProcess(pr, message, count, outError);
1914     }
1915
1916     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1917     p = message;
1918     q = buf;
1919     left = count;
1920     newcount = 0;
1921     while (left) {
1922         if (q >= buflim) {
1923             if (appData.debugMode) {
1924                 fprintf(debugFP, ">ICS: ");
1925                 show_bytes(debugFP, buf, newcount);
1926                 fprintf(debugFP, "\n");
1927             }
1928             outcount = OutputToProcess(pr, buf, newcount, outError);
1929             if (outcount < newcount) return -1; /* to be sure */
1930             q = buf;
1931             newcount = 0;
1932         }
1933         if (*p == '\n') {
1934             *q++ = '\r';
1935             newcount++;
1936         } else if (((unsigned char) *p) == TN_IAC) {
1937             *q++ = (char) TN_IAC;
1938             newcount ++;
1939         }
1940         *q++ = *p++;
1941         newcount++;
1942         left--;
1943     }
1944     if (appData.debugMode) {
1945         fprintf(debugFP, ">ICS: ");
1946         show_bytes(debugFP, buf, newcount);
1947         fprintf(debugFP, "\n");
1948     }
1949     outcount = OutputToProcess(pr, buf, newcount, outError);
1950     if (outcount < newcount) return -1; /* to be sure */
1951     return count;
1952 }
1953
1954 void
1955 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1956 {
1957     int outError, outCount;
1958     static int gotEof = 0;
1959     static FILE *ini;
1960
1961     /* Pass data read from player on to ICS */
1962     if (count > 0) {
1963         gotEof = 0;
1964         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1965         if (outCount < count) {
1966             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1967         }
1968         if(have_sent_ICS_logon == 2) {
1969           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1970             fprintf(ini, "%s", message);
1971             have_sent_ICS_logon = 3;
1972           } else
1973             have_sent_ICS_logon = 1;
1974         } else if(have_sent_ICS_logon == 3) {
1975             fprintf(ini, "%s", message);
1976             fclose(ini);
1977           have_sent_ICS_logon = 1;
1978         }
1979     } else if (count < 0) {
1980         RemoveInputSource(isr);
1981         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1982     } else if (gotEof++ > 0) {
1983         RemoveInputSource(isr);
1984         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1985     }
1986 }
1987
1988 void
1989 KeepAlive ()
1990 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1991     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1992     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1993     SendToICS("date\n");
1994     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1995 }
1996
1997 /* added routine for printf style output to ics */
1998 void
1999 ics_printf (char *format, ...)
2000 {
2001     char buffer[MSG_SIZ];
2002     va_list args;
2003
2004     va_start(args, format);
2005     vsnprintf(buffer, sizeof(buffer), format, args);
2006     buffer[sizeof(buffer)-1] = '\0';
2007     SendToICS(buffer);
2008     va_end(args);
2009 }
2010
2011 void
2012 SendToICS (char *s)
2013 {
2014     int count, outCount, outError;
2015
2016     if (icsPR == NoProc) return;
2017
2018     count = strlen(s);
2019     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2020     if (outCount < count) {
2021         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2022     }
2023 }
2024
2025 /* This is used for sending logon scripts to the ICS. Sending
2026    without a delay causes problems when using timestamp on ICC
2027    (at least on my machine). */
2028 void
2029 SendToICSDelayed (char *s, long msdelay)
2030 {
2031     int count, outCount, outError;
2032
2033     if (icsPR == NoProc) return;
2034
2035     count = strlen(s);
2036     if (appData.debugMode) {
2037         fprintf(debugFP, ">ICS: ");
2038         show_bytes(debugFP, s, count);
2039         fprintf(debugFP, "\n");
2040     }
2041     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2042                                       msdelay);
2043     if (outCount < count) {
2044         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2045     }
2046 }
2047
2048
2049 /* Remove all highlighting escape sequences in s
2050    Also deletes any suffix starting with '('
2051    */
2052 char *
2053 StripHighlightAndTitle (char *s)
2054 {
2055     static char retbuf[MSG_SIZ];
2056     char *p = retbuf;
2057
2058     while (*s != NULLCHAR) {
2059         while (*s == '\033') {
2060             while (*s != NULLCHAR && !isalpha(*s)) s++;
2061             if (*s != NULLCHAR) s++;
2062         }
2063         while (*s != NULLCHAR && *s != '\033') {
2064             if (*s == '(' || *s == '[') {
2065                 *p = NULLCHAR;
2066                 return retbuf;
2067             }
2068             *p++ = *s++;
2069         }
2070     }
2071     *p = NULLCHAR;
2072     return retbuf;
2073 }
2074
2075 /* Remove all highlighting escape sequences in s */
2076 char *
2077 StripHighlight (char *s)
2078 {
2079     static char retbuf[MSG_SIZ];
2080     char *p = retbuf;
2081
2082     while (*s != NULLCHAR) {
2083         while (*s == '\033') {
2084             while (*s != NULLCHAR && !isalpha(*s)) s++;
2085             if (*s != NULLCHAR) s++;
2086         }
2087         while (*s != NULLCHAR && *s != '\033') {
2088             *p++ = *s++;
2089         }
2090     }
2091     *p = NULLCHAR;
2092     return retbuf;
2093 }
2094
2095 char engineVariant[MSG_SIZ];
2096 char *variantNames[] = VARIANT_NAMES;
2097 char *
2098 VariantName (VariantClass v)
2099 {
2100     if(v == VariantUnknown || *engineVariant) return engineVariant;
2101     return variantNames[v];
2102 }
2103
2104
2105 /* Identify a variant from the strings the chess servers use or the
2106    PGN Variant tag names we use. */
2107 VariantClass
2108 StringToVariant (char *e)
2109 {
2110     char *p;
2111     int wnum = -1;
2112     VariantClass v = VariantNormal;
2113     int i, found = FALSE;
2114     char buf[MSG_SIZ], c;
2115     int len;
2116
2117     if (!e) return v;
2118
2119     /* [HGM] skip over optional board-size prefixes */
2120     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2121         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2122         while( *e++ != '_');
2123     }
2124
2125     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2126         v = VariantNormal;
2127         found = TRUE;
2128     } else
2129     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2130       if (p = StrCaseStr(e, variantNames[i])) {
2131         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2132         v = (VariantClass) i;
2133         found = TRUE;
2134         break;
2135       }
2136     }
2137
2138     if (!found) {
2139       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2140           || StrCaseStr(e, "wild/fr")
2141           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2142         v = VariantFischeRandom;
2143       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2144                  (i = 1, p = StrCaseStr(e, "w"))) {
2145         p += i;
2146         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2147         if (isdigit(*p)) {
2148           wnum = atoi(p);
2149         } else {
2150           wnum = -1;
2151         }
2152         switch (wnum) {
2153         case 0: /* FICS only, actually */
2154         case 1:
2155           /* Castling legal even if K starts on d-file */
2156           v = VariantWildCastle;
2157           break;
2158         case 2:
2159         case 3:
2160         case 4:
2161           /* Castling illegal even if K & R happen to start in
2162              normal positions. */
2163           v = VariantNoCastle;
2164           break;
2165         case 5:
2166         case 7:
2167         case 8:
2168         case 10:
2169         case 11:
2170         case 12:
2171         case 13:
2172         case 14:
2173         case 15:
2174         case 18:
2175         case 19:
2176           /* Castling legal iff K & R start in normal positions */
2177           v = VariantNormal;
2178           break;
2179         case 6:
2180         case 20:
2181         case 21:
2182           /* Special wilds for position setup; unclear what to do here */
2183           v = VariantLoadable;
2184           break;
2185         case 9:
2186           /* Bizarre ICC game */
2187           v = VariantTwoKings;
2188           break;
2189         case 16:
2190           v = VariantKriegspiel;
2191           break;
2192         case 17:
2193           v = VariantLosers;
2194           break;
2195         case 22:
2196           v = VariantFischeRandom;
2197           break;
2198         case 23:
2199           v = VariantCrazyhouse;
2200           break;
2201         case 24:
2202           v = VariantBughouse;
2203           break;
2204         case 25:
2205           v = Variant3Check;
2206           break;
2207         case 26:
2208           /* Not quite the same as FICS suicide! */
2209           v = VariantGiveaway;
2210           break;
2211         case 27:
2212           v = VariantAtomic;
2213           break;
2214         case 28:
2215           v = VariantShatranj;
2216           break;
2217
2218         /* Temporary names for future ICC types.  The name *will* change in
2219            the next xboard/WinBoard release after ICC defines it. */
2220         case 29:
2221           v = Variant29;
2222           break;
2223         case 30:
2224           v = Variant30;
2225           break;
2226         case 31:
2227           v = Variant31;
2228           break;
2229         case 32:
2230           v = Variant32;
2231           break;
2232         case 33:
2233           v = Variant33;
2234           break;
2235         case 34:
2236           v = Variant34;
2237           break;
2238         case 35:
2239           v = Variant35;
2240           break;
2241         case 36:
2242           v = Variant36;
2243           break;
2244         case 37:
2245           v = VariantShogi;
2246           break;
2247         case 38:
2248           v = VariantXiangqi;
2249           break;
2250         case 39:
2251           v = VariantCourier;
2252           break;
2253         case 40:
2254           v = VariantGothic;
2255           break;
2256         case 41:
2257           v = VariantCapablanca;
2258           break;
2259         case 42:
2260           v = VariantKnightmate;
2261           break;
2262         case 43:
2263           v = VariantFairy;
2264           break;
2265         case 44:
2266           v = VariantCylinder;
2267           break;
2268         case 45:
2269           v = VariantFalcon;
2270           break;
2271         case 46:
2272           v = VariantCapaRandom;
2273           break;
2274         case 47:
2275           v = VariantBerolina;
2276           break;
2277         case 48:
2278           v = VariantJanus;
2279           break;
2280         case 49:
2281           v = VariantSuper;
2282           break;
2283         case 50:
2284           v = VariantGreat;
2285           break;
2286         case -1:
2287           /* Found "wild" or "w" in the string but no number;
2288              must assume it's normal chess. */
2289           v = VariantNormal;
2290           break;
2291         default:
2292           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2293           if( (len >= MSG_SIZ) && appData.debugMode )
2294             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2295
2296           DisplayError(buf, 0);
2297           v = VariantUnknown;
2298           break;
2299         }
2300       }
2301     }
2302     if (appData.debugMode) {
2303       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2304               e, wnum, VariantName(v));
2305     }
2306     return v;
2307 }
2308
2309 static int leftover_start = 0, leftover_len = 0;
2310 char star_match[STAR_MATCH_N][MSG_SIZ];
2311
2312 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2313    advance *index beyond it, and set leftover_start to the new value of
2314    *index; else return FALSE.  If pattern contains the character '*', it
2315    matches any sequence of characters not containing '\r', '\n', or the
2316    character following the '*' (if any), and the matched sequence(s) are
2317    copied into star_match.
2318    */
2319 int
2320 looking_at ( char *buf, int *index, char *pattern)
2321 {
2322     char *bufp = &buf[*index], *patternp = pattern;
2323     int star_count = 0;
2324     char *matchp = star_match[0];
2325
2326     for (;;) {
2327         if (*patternp == NULLCHAR) {
2328             *index = leftover_start = bufp - buf;
2329             *matchp = NULLCHAR;
2330             return TRUE;
2331         }
2332         if (*bufp == NULLCHAR) return FALSE;
2333         if (*patternp == '*') {
2334             if (*bufp == *(patternp + 1)) {
2335                 *matchp = NULLCHAR;
2336                 matchp = star_match[++star_count];
2337                 patternp += 2;
2338                 bufp++;
2339                 continue;
2340             } else if (*bufp == '\n' || *bufp == '\r') {
2341                 patternp++;
2342                 if (*patternp == NULLCHAR)
2343                   continue;
2344                 else
2345                   return FALSE;
2346             } else {
2347                 *matchp++ = *bufp++;
2348                 continue;
2349             }
2350         }
2351         if (*patternp != *bufp) return FALSE;
2352         patternp++;
2353         bufp++;
2354     }
2355 }
2356
2357 void
2358 SendToPlayer (char *data, int length)
2359 {
2360     int error, outCount;
2361     outCount = OutputToProcess(NoProc, data, length, &error);
2362     if (outCount < length) {
2363         DisplayFatalError(_("Error writing to display"), error, 1);
2364     }
2365 }
2366
2367 void
2368 PackHolding (char packed[], char *holding)
2369 {
2370     char *p = holding;
2371     char *q = packed;
2372     int runlength = 0;
2373     int curr = 9999;
2374     do {
2375         if (*p == curr) {
2376             runlength++;
2377         } else {
2378             switch (runlength) {
2379               case 0:
2380                 break;
2381               case 1:
2382                 *q++ = curr;
2383                 break;
2384               case 2:
2385                 *q++ = curr;
2386                 *q++ = curr;
2387                 break;
2388               default:
2389                 sprintf(q, "%d", runlength);
2390                 while (*q) q++;
2391                 *q++ = curr;
2392                 break;
2393             }
2394             runlength = 1;
2395             curr = *p;
2396         }
2397     } while (*p++);
2398     *q = NULLCHAR;
2399 }
2400
2401 /* Telnet protocol requests from the front end */
2402 void
2403 TelnetRequest (unsigned char ddww, unsigned char option)
2404 {
2405     unsigned char msg[3];
2406     int outCount, outError;
2407
2408     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2409
2410     if (appData.debugMode) {
2411         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2412         switch (ddww) {
2413           case TN_DO:
2414             ddwwStr = "DO";
2415             break;
2416           case TN_DONT:
2417             ddwwStr = "DONT";
2418             break;
2419           case TN_WILL:
2420             ddwwStr = "WILL";
2421             break;
2422           case TN_WONT:
2423             ddwwStr = "WONT";
2424             break;
2425           default:
2426             ddwwStr = buf1;
2427             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2428             break;
2429         }
2430         switch (option) {
2431           case TN_ECHO:
2432             optionStr = "ECHO";
2433             break;
2434           default:
2435             optionStr = buf2;
2436             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2437             break;
2438         }
2439         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2440     }
2441     msg[0] = TN_IAC;
2442     msg[1] = ddww;
2443     msg[2] = option;
2444     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2445     if (outCount < 3) {
2446         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2447     }
2448 }
2449
2450 void
2451 DoEcho ()
2452 {
2453     if (!appData.icsActive) return;
2454     TelnetRequest(TN_DO, TN_ECHO);
2455 }
2456
2457 void
2458 DontEcho ()
2459 {
2460     if (!appData.icsActive) return;
2461     TelnetRequest(TN_DONT, TN_ECHO);
2462 }
2463
2464 void
2465 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2466 {
2467     /* put the holdings sent to us by the server on the board holdings area */
2468     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2469     char p;
2470     ChessSquare piece;
2471
2472     if(gameInfo.holdingsWidth < 2)  return;
2473     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2474         return; // prevent overwriting by pre-board holdings
2475
2476     if( (int)lowestPiece >= BlackPawn ) {
2477         holdingsColumn = 0;
2478         countsColumn = 1;
2479         holdingsStartRow = BOARD_HEIGHT-1;
2480         direction = -1;
2481     } else {
2482         holdingsColumn = BOARD_WIDTH-1;
2483         countsColumn = BOARD_WIDTH-2;
2484         holdingsStartRow = 0;
2485         direction = 1;
2486     }
2487
2488     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2489         board[i][holdingsColumn] = EmptySquare;
2490         board[i][countsColumn]   = (ChessSquare) 0;
2491     }
2492     while( (p=*holdings++) != NULLCHAR ) {
2493         piece = CharToPiece( ToUpper(p) );
2494         if(piece == EmptySquare) continue;
2495         /*j = (int) piece - (int) WhitePawn;*/
2496         j = PieceToNumber(piece);
2497         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2498         if(j < 0) continue;               /* should not happen */
2499         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2500         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2501         board[holdingsStartRow+j*direction][countsColumn]++;
2502     }
2503 }
2504
2505
2506 void
2507 VariantSwitch (Board board, VariantClass newVariant)
2508 {
2509    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2510    static Board oldBoard;
2511
2512    startedFromPositionFile = FALSE;
2513    if(gameInfo.variant == newVariant) return;
2514
2515    /* [HGM] This routine is called each time an assignment is made to
2516     * gameInfo.variant during a game, to make sure the board sizes
2517     * are set to match the new variant. If that means adding or deleting
2518     * holdings, we shift the playing board accordingly
2519     * This kludge is needed because in ICS observe mode, we get boards
2520     * of an ongoing game without knowing the variant, and learn about the
2521     * latter only later. This can be because of the move list we requested,
2522     * in which case the game history is refilled from the beginning anyway,
2523     * but also when receiving holdings of a crazyhouse game. In the latter
2524     * case we want to add those holdings to the already received position.
2525     */
2526
2527
2528    if (appData.debugMode) {
2529      fprintf(debugFP, "Switch board from %s to %s\n",
2530              VariantName(gameInfo.variant), VariantName(newVariant));
2531      setbuf(debugFP, NULL);
2532    }
2533    shuffleOpenings = 0;       /* [HGM] shuffle */
2534    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2535    switch(newVariant)
2536      {
2537      case VariantShogi:
2538        newWidth = 9;  newHeight = 9;
2539        gameInfo.holdingsSize = 7;
2540      case VariantBughouse:
2541      case VariantCrazyhouse:
2542        newHoldingsWidth = 2; break;
2543      case VariantGreat:
2544        newWidth = 10;
2545      case VariantSuper:
2546        newHoldingsWidth = 2;
2547        gameInfo.holdingsSize = 8;
2548        break;
2549      case VariantGothic:
2550      case VariantCapablanca:
2551      case VariantCapaRandom:
2552        newWidth = 10;
2553      default:
2554        newHoldingsWidth = gameInfo.holdingsSize = 0;
2555      };
2556
2557    if(newWidth  != gameInfo.boardWidth  ||
2558       newHeight != gameInfo.boardHeight ||
2559       newHoldingsWidth != gameInfo.holdingsWidth ) {
2560
2561      /* shift position to new playing area, if needed */
2562      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2563        for(i=0; i<BOARD_HEIGHT; i++)
2564          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2565            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2566              board[i][j];
2567        for(i=0; i<newHeight; i++) {
2568          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2569          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2570        }
2571      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2572        for(i=0; i<BOARD_HEIGHT; i++)
2573          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2574            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2575              board[i][j];
2576      }
2577      board[HOLDINGS_SET] = 0;
2578      gameInfo.boardWidth  = newWidth;
2579      gameInfo.boardHeight = newHeight;
2580      gameInfo.holdingsWidth = newHoldingsWidth;
2581      gameInfo.variant = newVariant;
2582      InitDrawingSizes(-2, 0);
2583    } else gameInfo.variant = newVariant;
2584    CopyBoard(oldBoard, board);   // remember correctly formatted board
2585      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2586    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2587 }
2588
2589 static int loggedOn = FALSE;
2590
2591 /*-- Game start info cache: --*/
2592 int gs_gamenum;
2593 char gs_kind[MSG_SIZ];
2594 static char player1Name[128] = "";
2595 static char player2Name[128] = "";
2596 static char cont_seq[] = "\n\\   ";
2597 static int player1Rating = -1;
2598 static int player2Rating = -1;
2599 /*----------------------------*/
2600
2601 ColorClass curColor = ColorNormal;
2602 int suppressKibitz = 0;
2603
2604 // [HGM] seekgraph
2605 Boolean soughtPending = FALSE;
2606 Boolean seekGraphUp;
2607 #define MAX_SEEK_ADS 200
2608 #define SQUARE 0x80
2609 char *seekAdList[MAX_SEEK_ADS];
2610 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2611 float tcList[MAX_SEEK_ADS];
2612 char colorList[MAX_SEEK_ADS];
2613 int nrOfSeekAds = 0;
2614 int minRating = 1010, maxRating = 2800;
2615 int hMargin = 10, vMargin = 20, h, w;
2616 extern int squareSize, lineGap;
2617
2618 void
2619 PlotSeekAd (int i)
2620 {
2621         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2622         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2623         if(r < minRating+100 && r >=0 ) r = minRating+100;
2624         if(r > maxRating) r = maxRating;
2625         if(tc < 1.f) tc = 1.f;
2626         if(tc > 95.f) tc = 95.f;
2627         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2628         y = ((double)r - minRating)/(maxRating - minRating)
2629             * (h-vMargin-squareSize/8-1) + vMargin;
2630         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2631         if(strstr(seekAdList[i], " u ")) color = 1;
2632         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2633            !strstr(seekAdList[i], "bullet") &&
2634            !strstr(seekAdList[i], "blitz") &&
2635            !strstr(seekAdList[i], "standard") ) color = 2;
2636         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2637         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2638 }
2639
2640 void
2641 PlotSingleSeekAd (int i)
2642 {
2643         PlotSeekAd(i);
2644 }
2645
2646 void
2647 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2648 {
2649         char buf[MSG_SIZ], *ext = "";
2650         VariantClass v = StringToVariant(type);
2651         if(strstr(type, "wild")) {
2652             ext = type + 4; // append wild number
2653             if(v == VariantFischeRandom) type = "chess960"; else
2654             if(v == VariantLoadable) type = "setup"; else
2655             type = VariantName(v);
2656         }
2657         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2658         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2659             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2660             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2661             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2662             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2663             seekNrList[nrOfSeekAds] = nr;
2664             zList[nrOfSeekAds] = 0;
2665             seekAdList[nrOfSeekAds++] = StrSave(buf);
2666             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2667         }
2668 }
2669
2670 void
2671 EraseSeekDot (int i)
2672 {
2673     int x = xList[i], y = yList[i], d=squareSize/4, k;
2674     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2675     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2676     // now replot every dot that overlapped
2677     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2678         int xx = xList[k], yy = yList[k];
2679         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2680             DrawSeekDot(xx, yy, colorList[k]);
2681     }
2682 }
2683
2684 void
2685 RemoveSeekAd (int nr)
2686 {
2687         int i;
2688         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2689             EraseSeekDot(i);
2690             if(seekAdList[i]) free(seekAdList[i]);
2691             seekAdList[i] = seekAdList[--nrOfSeekAds];
2692             seekNrList[i] = seekNrList[nrOfSeekAds];
2693             ratingList[i] = ratingList[nrOfSeekAds];
2694             colorList[i]  = colorList[nrOfSeekAds];
2695             tcList[i] = tcList[nrOfSeekAds];
2696             xList[i]  = xList[nrOfSeekAds];
2697             yList[i]  = yList[nrOfSeekAds];
2698             zList[i]  = zList[nrOfSeekAds];
2699             seekAdList[nrOfSeekAds] = NULL;
2700             break;
2701         }
2702 }
2703
2704 Boolean
2705 MatchSoughtLine (char *line)
2706 {
2707     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2708     int nr, base, inc, u=0; char dummy;
2709
2710     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2711        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2712        (u=1) &&
2713        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2714         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2715         // match: compact and save the line
2716         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2717         return TRUE;
2718     }
2719     return FALSE;
2720 }
2721
2722 int
2723 DrawSeekGraph ()
2724 {
2725     int i;
2726     if(!seekGraphUp) return FALSE;
2727     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2728     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2729
2730     DrawSeekBackground(0, 0, w, h);
2731     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2732     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2733     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2734         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2735         yy = h-1-yy;
2736         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2737         if(i%500 == 0) {
2738             char buf[MSG_SIZ];
2739             snprintf(buf, MSG_SIZ, "%d", i);
2740             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2741         }
2742     }
2743     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2744     for(i=1; i<100; i+=(i<10?1:5)) {
2745         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2746         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2747         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2748             char buf[MSG_SIZ];
2749             snprintf(buf, MSG_SIZ, "%d", i);
2750             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2751         }
2752     }
2753     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2754     return TRUE;
2755 }
2756
2757 int
2758 SeekGraphClick (ClickType click, int x, int y, int moving)
2759 {
2760     static int lastDown = 0, displayed = 0, lastSecond;
2761     if(y < 0) return FALSE;
2762     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2763         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2764         if(!seekGraphUp) return FALSE;
2765         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2766         DrawPosition(TRUE, NULL);
2767         return TRUE;
2768     }
2769     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2770         if(click == Release || moving) return FALSE;
2771         nrOfSeekAds = 0;
2772         soughtPending = TRUE;
2773         SendToICS(ics_prefix);
2774         SendToICS("sought\n"); // should this be "sought all"?
2775     } else { // issue challenge based on clicked ad
2776         int dist = 10000; int i, closest = 0, second = 0;
2777         for(i=0; i<nrOfSeekAds; i++) {
2778             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2779             if(d < dist) { dist = d; closest = i; }
2780             second += (d - zList[i] < 120); // count in-range ads
2781             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2782         }
2783         if(dist < 120) {
2784             char buf[MSG_SIZ];
2785             second = (second > 1);
2786             if(displayed != closest || second != lastSecond) {
2787                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2788                 lastSecond = second; displayed = closest;
2789             }
2790             if(click == Press) {
2791                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2792                 lastDown = closest;
2793                 return TRUE;
2794             } // on press 'hit', only show info
2795             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2796             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2797             SendToICS(ics_prefix);
2798             SendToICS(buf);
2799             return TRUE; // let incoming board of started game pop down the graph
2800         } else if(click == Release) { // release 'miss' is ignored
2801             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2802             if(moving == 2) { // right up-click
2803                 nrOfSeekAds = 0; // refresh graph
2804                 soughtPending = TRUE;
2805                 SendToICS(ics_prefix);
2806                 SendToICS("sought\n"); // should this be "sought all"?
2807             }
2808             return TRUE;
2809         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2810         // press miss or release hit 'pop down' seek graph
2811         seekGraphUp = FALSE;
2812         DrawPosition(TRUE, NULL);
2813     }
2814     return TRUE;
2815 }
2816
2817 void
2818 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2819 {
2820 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2821 #define STARTED_NONE 0
2822 #define STARTED_MOVES 1
2823 #define STARTED_BOARD 2
2824 #define STARTED_OBSERVE 3
2825 #define STARTED_HOLDINGS 4
2826 #define STARTED_CHATTER 5
2827 #define STARTED_COMMENT 6
2828 #define STARTED_MOVES_NOHIDE 7
2829
2830     static int started = STARTED_NONE;
2831     static char parse[20000];
2832     static int parse_pos = 0;
2833     static char buf[BUF_SIZE + 1];
2834     static int firstTime = TRUE, intfSet = FALSE;
2835     static ColorClass prevColor = ColorNormal;
2836     static int savingComment = FALSE;
2837     static int cmatch = 0; // continuation sequence match
2838     char *bp;
2839     char str[MSG_SIZ];
2840     int i, oldi;
2841     int buf_len;
2842     int next_out;
2843     int tkind;
2844     int backup;    /* [DM] For zippy color lines */
2845     char *p;
2846     char talker[MSG_SIZ]; // [HGM] chat
2847     int channel, collective=0;
2848
2849     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2850
2851     if (appData.debugMode) {
2852       if (!error) {
2853         fprintf(debugFP, "<ICS: ");
2854         show_bytes(debugFP, data, count);
2855         fprintf(debugFP, "\n");
2856       }
2857     }
2858
2859     if (appData.debugMode) { int f = forwardMostMove;
2860         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2861                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2862                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2863     }
2864     if (count > 0) {
2865         /* If last read ended with a partial line that we couldn't parse,
2866            prepend it to the new read and try again. */
2867         if (leftover_len > 0) {
2868             for (i=0; i<leftover_len; i++)
2869               buf[i] = buf[leftover_start + i];
2870         }
2871
2872     /* copy new characters into the buffer */
2873     bp = buf + leftover_len;
2874     buf_len=leftover_len;
2875     for (i=0; i<count; i++)
2876     {
2877         // ignore these
2878         if (data[i] == '\r')
2879             continue;
2880
2881         // join lines split by ICS?
2882         if (!appData.noJoin)
2883         {
2884             /*
2885                 Joining just consists of finding matches against the
2886                 continuation sequence, and discarding that sequence
2887                 if found instead of copying it.  So, until a match
2888                 fails, there's nothing to do since it might be the
2889                 complete sequence, and thus, something we don't want
2890                 copied.
2891             */
2892             if (data[i] == cont_seq[cmatch])
2893             {
2894                 cmatch++;
2895                 if (cmatch == strlen(cont_seq))
2896                 {
2897                     cmatch = 0; // complete match.  just reset the counter
2898
2899                     /*
2900                         it's possible for the ICS to not include the space
2901                         at the end of the last word, making our [correct]
2902                         join operation fuse two separate words.  the server
2903                         does this when the space occurs at the width setting.
2904                     */
2905                     if (!buf_len || buf[buf_len-1] != ' ')
2906                     {
2907                         *bp++ = ' ';
2908                         buf_len++;
2909                     }
2910                 }
2911                 continue;
2912             }
2913             else if (cmatch)
2914             {
2915                 /*
2916                     match failed, so we have to copy what matched before
2917                     falling through and copying this character.  In reality,
2918                     this will only ever be just the newline character, but
2919                     it doesn't hurt to be precise.
2920                 */
2921                 strncpy(bp, cont_seq, cmatch);
2922                 bp += cmatch;
2923                 buf_len += cmatch;
2924                 cmatch = 0;
2925             }
2926         }
2927
2928         // copy this char
2929         *bp++ = data[i];
2930         buf_len++;
2931     }
2932
2933         buf[buf_len] = NULLCHAR;
2934 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2935         next_out = 0;
2936         leftover_start = 0;
2937
2938         i = 0;
2939         while (i < buf_len) {
2940             /* Deal with part of the TELNET option negotiation
2941                protocol.  We refuse to do anything beyond the
2942                defaults, except that we allow the WILL ECHO option,
2943                which ICS uses to turn off password echoing when we are
2944                directly connected to it.  We reject this option
2945                if localLineEditing mode is on (always on in xboard)
2946                and we are talking to port 23, which might be a real
2947                telnet server that will try to keep WILL ECHO on permanently.
2948              */
2949             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2950                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2951                 unsigned char option;
2952                 oldi = i;
2953                 switch ((unsigned char) buf[++i]) {
2954                   case TN_WILL:
2955                     if (appData.debugMode)
2956                       fprintf(debugFP, "\n<WILL ");
2957                     switch (option = (unsigned char) buf[++i]) {
2958                       case TN_ECHO:
2959                         if (appData.debugMode)
2960                           fprintf(debugFP, "ECHO ");
2961                         /* Reply only if this is a change, according
2962                            to the protocol rules. */
2963                         if (remoteEchoOption) break;
2964                         if (appData.localLineEditing &&
2965                             atoi(appData.icsPort) == TN_PORT) {
2966                             TelnetRequest(TN_DONT, TN_ECHO);
2967                         } else {
2968                             EchoOff();
2969                             TelnetRequest(TN_DO, TN_ECHO);
2970                             remoteEchoOption = TRUE;
2971                         }
2972                         break;
2973                       default:
2974                         if (appData.debugMode)
2975                           fprintf(debugFP, "%d ", option);
2976                         /* Whatever this is, we don't want it. */
2977                         TelnetRequest(TN_DONT, option);
2978                         break;
2979                     }
2980                     break;
2981                   case TN_WONT:
2982                     if (appData.debugMode)
2983                       fprintf(debugFP, "\n<WONT ");
2984                     switch (option = (unsigned char) buf[++i]) {
2985                       case TN_ECHO:
2986                         if (appData.debugMode)
2987                           fprintf(debugFP, "ECHO ");
2988                         /* Reply only if this is a change, according
2989                            to the protocol rules. */
2990                         if (!remoteEchoOption) break;
2991                         EchoOn();
2992                         TelnetRequest(TN_DONT, TN_ECHO);
2993                         remoteEchoOption = FALSE;
2994                         break;
2995                       default:
2996                         if (appData.debugMode)
2997                           fprintf(debugFP, "%d ", (unsigned char) option);
2998                         /* Whatever this is, it must already be turned
2999                            off, because we never agree to turn on
3000                            anything non-default, so according to the
3001                            protocol rules, we don't reply. */
3002                         break;
3003                     }
3004                     break;
3005                   case TN_DO:
3006                     if (appData.debugMode)
3007                       fprintf(debugFP, "\n<DO ");
3008                     switch (option = (unsigned char) buf[++i]) {
3009                       default:
3010                         /* Whatever this is, we refuse to do it. */
3011                         if (appData.debugMode)
3012                           fprintf(debugFP, "%d ", option);
3013                         TelnetRequest(TN_WONT, option);
3014                         break;
3015                     }
3016                     break;
3017                   case TN_DONT:
3018                     if (appData.debugMode)
3019                       fprintf(debugFP, "\n<DONT ");
3020                     switch (option = (unsigned char) buf[++i]) {
3021                       default:
3022                         if (appData.debugMode)
3023                           fprintf(debugFP, "%d ", option);
3024                         /* Whatever this is, we are already not doing
3025                            it, because we never agree to do anything
3026                            non-default, so according to the protocol
3027                            rules, we don't reply. */
3028                         break;
3029                     }
3030                     break;
3031                   case TN_IAC:
3032                     if (appData.debugMode)
3033                       fprintf(debugFP, "\n<IAC ");
3034                     /* Doubled IAC; pass it through */
3035                     i--;
3036                     break;
3037                   default:
3038                     if (appData.debugMode)
3039                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3040                     /* Drop all other telnet commands on the floor */
3041                     break;
3042                 }
3043                 if (oldi > next_out)
3044                   SendToPlayer(&buf[next_out], oldi - next_out);
3045                 if (++i > next_out)
3046                   next_out = i;
3047                 continue;
3048             }
3049
3050             /* OK, this at least will *usually* work */
3051             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3052                 loggedOn = TRUE;
3053             }
3054
3055             if (loggedOn && !intfSet) {
3056                 if (ics_type == ICS_ICC) {
3057                   snprintf(str, MSG_SIZ,
3058                           "/set-quietly interface %s\n/set-quietly style 12\n",
3059                           programVersion);
3060                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3061                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3062                 } else if (ics_type == ICS_CHESSNET) {
3063                   snprintf(str, MSG_SIZ, "/style 12\n");
3064                 } else {
3065                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3066                   strcat(str, programVersion);
3067                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3068                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3069                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3070 #ifdef WIN32
3071                   strcat(str, "$iset nohighlight 1\n");
3072 #endif
3073                   strcat(str, "$iset lock 1\n$style 12\n");
3074                 }
3075                 SendToICS(str);
3076                 NotifyFrontendLogin();
3077                 intfSet = TRUE;
3078             }
3079
3080             if (started == STARTED_COMMENT) {
3081                 /* Accumulate characters in comment */
3082                 parse[parse_pos++] = buf[i];
3083                 if (buf[i] == '\n') {
3084                     parse[parse_pos] = NULLCHAR;
3085                     if(chattingPartner>=0) {
3086                         char mess[MSG_SIZ];
3087                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3088                         OutputChatMessage(chattingPartner, mess);
3089                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3090                             int p;
3091                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3092                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3093                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3094                                 OutputChatMessage(p, mess);
3095                                 break;
3096                             }
3097                         }
3098                         chattingPartner = -1;
3099                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3100                         collective = 0;
3101                     } else
3102                     if(!suppressKibitz) // [HGM] kibitz
3103                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3104                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3105                         int nrDigit = 0, nrAlph = 0, j;
3106                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3107                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3108                         parse[parse_pos] = NULLCHAR;
3109                         // try to be smart: if it does not look like search info, it should go to
3110                         // ICS interaction window after all, not to engine-output window.
3111                         for(j=0; j<parse_pos; j++) { // count letters and digits
3112                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3113                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3114                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3115                         }
3116                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3117                             int depth=0; float score;
3118                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3119                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3120                                 pvInfoList[forwardMostMove-1].depth = depth;
3121                                 pvInfoList[forwardMostMove-1].score = 100*score;
3122                             }
3123                             OutputKibitz(suppressKibitz, parse);
3124                         } else {
3125                             char tmp[MSG_SIZ];
3126                             if(gameMode == IcsObserving) // restore original ICS messages
3127                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3128                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3129                             else
3130                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3131                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3132                             SendToPlayer(tmp, strlen(tmp));
3133                         }
3134                         next_out = i+1; // [HGM] suppress printing in ICS window
3135                     }
3136                     started = STARTED_NONE;
3137                 } else {
3138                     /* Don't match patterns against characters in comment */
3139                     i++;
3140                     continue;
3141                 }
3142             }
3143             if (started == STARTED_CHATTER) {
3144                 if (buf[i] != '\n') {
3145                     /* Don't match patterns against characters in chatter */
3146                     i++;
3147                     continue;
3148                 }
3149                 started = STARTED_NONE;
3150                 if(suppressKibitz) next_out = i+1;
3151             }
3152
3153             /* Kludge to deal with rcmd protocol */
3154             if (firstTime && looking_at(buf, &i, "\001*")) {
3155                 DisplayFatalError(&buf[1], 0, 1);
3156                 continue;
3157             } else {
3158                 firstTime = FALSE;
3159             }
3160
3161             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3162                 ics_type = ICS_ICC;
3163                 ics_prefix = "/";
3164                 if (appData.debugMode)
3165                   fprintf(debugFP, "ics_type %d\n", ics_type);
3166                 continue;
3167             }
3168             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3169                 ics_type = ICS_FICS;
3170                 ics_prefix = "$";
3171                 if (appData.debugMode)
3172                   fprintf(debugFP, "ics_type %d\n", ics_type);
3173                 continue;
3174             }
3175             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3176                 ics_type = ICS_CHESSNET;
3177                 ics_prefix = "/";
3178                 if (appData.debugMode)
3179                   fprintf(debugFP, "ics_type %d\n", ics_type);
3180                 continue;
3181             }
3182
3183             if (!loggedOn &&
3184                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3185                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3186                  looking_at(buf, &i, "will be \"*\""))) {
3187               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3188               continue;
3189             }
3190
3191             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3192               char buf[MSG_SIZ];
3193               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3194               DisplayIcsInteractionTitle(buf);
3195               have_set_title = TRUE;
3196             }
3197
3198             /* skip finger notes */
3199             if (started == STARTED_NONE &&
3200                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3201                  (buf[i] == '1' && buf[i+1] == '0')) &&
3202                 buf[i+2] == ':' && buf[i+3] == ' ') {
3203               started = STARTED_CHATTER;
3204               i += 3;
3205               continue;
3206             }
3207
3208             oldi = i;
3209             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3210             if(appData.seekGraph) {
3211                 if(soughtPending && MatchSoughtLine(buf+i)) {
3212                     i = strstr(buf+i, "rated") - buf;
3213                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3214                     next_out = leftover_start = i;
3215                     started = STARTED_CHATTER;
3216                     suppressKibitz = TRUE;
3217                     continue;
3218                 }
3219                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3220                         && looking_at(buf, &i, "* ads displayed")) {
3221                     soughtPending = FALSE;
3222                     seekGraphUp = TRUE;
3223                     DrawSeekGraph();
3224                     continue;
3225                 }
3226                 if(appData.autoRefresh) {
3227                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3228                         int s = (ics_type == ICS_ICC); // ICC format differs
3229                         if(seekGraphUp)
3230                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3231                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3232                         looking_at(buf, &i, "*% "); // eat prompt
3233                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3234                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3235                         next_out = i; // suppress
3236                         continue;
3237                     }
3238                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3239                         char *p = star_match[0];
3240                         while(*p) {
3241                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3242                             while(*p && *p++ != ' '); // next
3243                         }
3244                         looking_at(buf, &i, "*% "); // eat prompt
3245                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3246                         next_out = i;
3247                         continue;
3248                     }
3249                 }
3250             }
3251
3252             /* skip formula vars */
3253             if (started == STARTED_NONE &&
3254                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3255               started = STARTED_CHATTER;
3256               i += 3;
3257               continue;
3258             }
3259
3260             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3261             if (appData.autoKibitz && started == STARTED_NONE &&
3262                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3263                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3264                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3265                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3266                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3267                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3268                         suppressKibitz = TRUE;
3269                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3270                         next_out = i;
3271                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3272                                 && (gameMode == IcsPlayingWhite)) ||
3273                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3274                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3275                             started = STARTED_CHATTER; // own kibitz we simply discard
3276                         else {
3277                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3278                             parse_pos = 0; parse[0] = NULLCHAR;
3279                             savingComment = TRUE;
3280                             suppressKibitz = gameMode != IcsObserving ? 2 :
3281                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3282                         }
3283                         continue;
3284                 } else
3285                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3286                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3287                          && atoi(star_match[0])) {
3288                     // suppress the acknowledgements of our own autoKibitz
3289                     char *p;
3290                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3291                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3292                     SendToPlayer(star_match[0], strlen(star_match[0]));
3293                     if(looking_at(buf, &i, "*% ")) // eat prompt
3294                         suppressKibitz = FALSE;
3295                     next_out = i;
3296                     continue;
3297                 }
3298             } // [HGM] kibitz: end of patch
3299
3300             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3301
3302             // [HGM] chat: intercept tells by users for which we have an open chat window
3303             channel = -1;
3304             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3305                                            looking_at(buf, &i, "* whispers:") ||
3306                                            looking_at(buf, &i, "* kibitzes:") ||
3307                                            looking_at(buf, &i, "* shouts:") ||
3308                                            looking_at(buf, &i, "* c-shouts:") ||
3309                                            looking_at(buf, &i, "--> * ") ||
3310                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3311                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3312                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3313                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3314                 int p;
3315                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3316                 chattingPartner = -1; collective = 0;
3317
3318                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3319                 for(p=0; p<MAX_CHAT; p++) {
3320                     collective = 1;
3321                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3322                     talker[0] = '['; strcat(talker, "] ");
3323                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3324                     chattingPartner = p; break;
3325                     }
3326                 } else
3327                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3328                 for(p=0; p<MAX_CHAT; p++) {
3329                     collective = 1;
3330                     if(!strcmp("kibitzes", chatPartner[p])) {
3331                         talker[0] = '['; strcat(talker, "] ");
3332                         chattingPartner = p; break;
3333                     }
3334                 } else
3335                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3336                 for(p=0; p<MAX_CHAT; p++) {
3337                     collective = 1;
3338                     if(!strcmp("whispers", chatPartner[p])) {
3339                         talker[0] = '['; strcat(talker, "] ");
3340                         chattingPartner = p; break;
3341                     }
3342                 } else
3343                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3344                   if(buf[i-8] == '-' && buf[i-3] == 't')
3345                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3346                     collective = 1;
3347                     if(!strcmp("c-shouts", chatPartner[p])) {
3348                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3349                         chattingPartner = p; break;
3350                     }
3351                   }
3352                   if(chattingPartner < 0)
3353                   for(p=0; p<MAX_CHAT; p++) {
3354                     collective = 1;
3355                     if(!strcmp("shouts", chatPartner[p])) {
3356                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3357                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3358                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3359                         chattingPartner = p; break;
3360                     }
3361                   }
3362                 }
3363                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3364                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3365                     talker[0] = 0;
3366                     Colorize(ColorTell, FALSE);
3367                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3368                     collective |= 2;
3369                     chattingPartner = p; break;
3370                 }
3371                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3372                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3373                     started = STARTED_COMMENT;
3374                     parse_pos = 0; parse[0] = NULLCHAR;
3375                     savingComment = 3 + chattingPartner; // counts as TRUE
3376                     if(collective == 3) i = oldi; else {
3377                         suppressKibitz = TRUE;
3378                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3379                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3380                         continue;
3381                     }
3382                 }
3383             } // [HGM] chat: end of patch
3384
3385           backup = i;
3386             if (appData.zippyTalk || appData.zippyPlay) {
3387                 /* [DM] Backup address for color zippy lines */
3388 #if ZIPPY
3389                if (loggedOn == TRUE)
3390                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3391                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3392 #endif
3393             } // [DM] 'else { ' deleted
3394                 if (
3395                     /* Regular tells and says */
3396                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3397                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3398                     looking_at(buf, &i, "* says: ") ||
3399                     /* Don't color "message" or "messages" output */
3400                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3401                     looking_at(buf, &i, "*. * at *:*: ") ||
3402                     looking_at(buf, &i, "--* (*:*): ") ||
3403                     /* Message notifications (same color as tells) */
3404                     looking_at(buf, &i, "* has left a message ") ||
3405                     looking_at(buf, &i, "* just sent you a message:\n") ||
3406                     /* Whispers and kibitzes */
3407                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3408                     looking_at(buf, &i, "* kibitzes: ") ||
3409                     /* Channel tells */
3410                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3411
3412                   if (tkind == 1 && strchr(star_match[0], ':')) {
3413                       /* Avoid "tells you:" spoofs in channels */
3414                      tkind = 3;
3415                   }
3416                   if (star_match[0][0] == NULLCHAR ||
3417                       strchr(star_match[0], ' ') ||
3418                       (tkind == 3 && strchr(star_match[1], ' '))) {
3419                     /* Reject bogus matches */
3420                     i = oldi;
3421                   } else {
3422                     if (appData.colorize) {
3423                       if (oldi > next_out) {
3424                         SendToPlayer(&buf[next_out], oldi - next_out);
3425                         next_out = oldi;
3426                       }
3427                       switch (tkind) {
3428                       case 1:
3429                         Colorize(ColorTell, FALSE);
3430                         curColor = ColorTell;
3431                         break;
3432                       case 2:
3433                         Colorize(ColorKibitz, FALSE);
3434                         curColor = ColorKibitz;
3435                         break;
3436                       case 3:
3437                         p = strrchr(star_match[1], '(');
3438                         if (p == NULL) {
3439                           p = star_match[1];
3440                         } else {
3441                           p++;
3442                         }
3443                         if (atoi(p) == 1) {
3444                           Colorize(ColorChannel1, FALSE);
3445                           curColor = ColorChannel1;
3446                         } else {
3447                           Colorize(ColorChannel, FALSE);
3448                           curColor = ColorChannel;
3449                         }
3450                         break;
3451                       case 5:
3452                         curColor = ColorNormal;
3453                         break;
3454                       }
3455                     }
3456                     if (started == STARTED_NONE && appData.autoComment &&
3457                         (gameMode == IcsObserving ||
3458                          gameMode == IcsPlayingWhite ||
3459                          gameMode == IcsPlayingBlack)) {
3460                       parse_pos = i - oldi;
3461                       memcpy(parse, &buf[oldi], parse_pos);
3462                       parse[parse_pos] = NULLCHAR;
3463                       started = STARTED_COMMENT;
3464                       savingComment = TRUE;
3465                     } else if(collective != 3) {
3466                       started = STARTED_CHATTER;
3467                       savingComment = FALSE;
3468                     }
3469                     loggedOn = TRUE;
3470                     continue;
3471                   }
3472                 }
3473
3474                 if (looking_at(buf, &i, "* s-shouts: ") ||
3475                     looking_at(buf, &i, "* c-shouts: ")) {
3476                     if (appData.colorize) {
3477                         if (oldi > next_out) {
3478                             SendToPlayer(&buf[next_out], oldi - next_out);
3479                             next_out = oldi;
3480                         }
3481                         Colorize(ColorSShout, FALSE);
3482                         curColor = ColorSShout;
3483                     }
3484                     loggedOn = TRUE;
3485                     started = STARTED_CHATTER;
3486                     continue;
3487                 }
3488
3489                 if (looking_at(buf, &i, "--->")) {
3490                     loggedOn = TRUE;
3491                     continue;
3492                 }
3493
3494                 if (looking_at(buf, &i, "* shouts: ") ||
3495                     looking_at(buf, &i, "--> ")) {
3496                     if (appData.colorize) {
3497                         if (oldi > next_out) {
3498                             SendToPlayer(&buf[next_out], oldi - next_out);
3499                             next_out = oldi;
3500                         }
3501                         Colorize(ColorShout, FALSE);
3502                         curColor = ColorShout;
3503                     }
3504                     loggedOn = TRUE;
3505                     started = STARTED_CHATTER;
3506                     continue;
3507                 }
3508
3509                 if (looking_at( buf, &i, "Challenge:")) {
3510                     if (appData.colorize) {
3511                         if (oldi > next_out) {
3512                             SendToPlayer(&buf[next_out], oldi - next_out);
3513                             next_out = oldi;
3514                         }
3515                         Colorize(ColorChallenge, FALSE);
3516                         curColor = ColorChallenge;
3517                     }
3518                     loggedOn = TRUE;
3519                     continue;
3520                 }
3521
3522                 if (looking_at(buf, &i, "* offers you") ||
3523                     looking_at(buf, &i, "* offers to be") ||
3524                     looking_at(buf, &i, "* would like to") ||
3525                     looking_at(buf, &i, "* requests to") ||
3526                     looking_at(buf, &i, "Your opponent offers") ||
3527                     looking_at(buf, &i, "Your opponent requests")) {
3528
3529                     if (appData.colorize) {
3530                         if (oldi > next_out) {
3531                             SendToPlayer(&buf[next_out], oldi - next_out);
3532                             next_out = oldi;
3533                         }
3534                         Colorize(ColorRequest, FALSE);
3535                         curColor = ColorRequest;
3536                     }
3537                     continue;
3538                 }
3539
3540                 if (looking_at(buf, &i, "* (*) seeking")) {
3541                     if (appData.colorize) {
3542                         if (oldi > next_out) {
3543                             SendToPlayer(&buf[next_out], oldi - next_out);
3544                             next_out = oldi;
3545                         }
3546                         Colorize(ColorSeek, FALSE);
3547                         curColor = ColorSeek;
3548                     }
3549                     continue;
3550             }
3551
3552           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3553
3554             if (looking_at(buf, &i, "\\   ")) {
3555                 if (prevColor != ColorNormal) {
3556                     if (oldi > next_out) {
3557                         SendToPlayer(&buf[next_out], oldi - next_out);
3558                         next_out = oldi;
3559                     }
3560                     Colorize(prevColor, TRUE);
3561                     curColor = prevColor;
3562                 }
3563                 if (savingComment) {
3564                     parse_pos = i - oldi;
3565                     memcpy(parse, &buf[oldi], parse_pos);
3566                     parse[parse_pos] = NULLCHAR;
3567                     started = STARTED_COMMENT;
3568                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3569                         chattingPartner = savingComment - 3; // kludge to remember the box
3570                 } else {
3571                     started = STARTED_CHATTER;
3572                 }
3573                 continue;
3574             }
3575
3576             if (looking_at(buf, &i, "Black Strength :") ||
3577                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3578                 looking_at(buf, &i, "<10>") ||
3579                 looking_at(buf, &i, "#@#")) {
3580                 /* Wrong board style */
3581                 loggedOn = TRUE;
3582                 SendToICS(ics_prefix);
3583                 SendToICS("set style 12\n");
3584                 SendToICS(ics_prefix);
3585                 SendToICS("refresh\n");
3586                 continue;
3587             }
3588
3589             if (looking_at(buf, &i, "login:")) {
3590               if (!have_sent_ICS_logon) {
3591                 if(ICSInitScript())
3592                   have_sent_ICS_logon = 1;
3593                 else // no init script was found
3594                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3595               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3596                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3597               }
3598                 continue;
3599             }
3600
3601             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3602                 (looking_at(buf, &i, "\n<12> ") ||
3603                  looking_at(buf, &i, "<12> "))) {
3604                 loggedOn = TRUE;
3605                 if (oldi > next_out) {
3606                     SendToPlayer(&buf[next_out], oldi - next_out);
3607                 }
3608                 next_out = i;
3609                 started = STARTED_BOARD;
3610                 parse_pos = 0;
3611                 continue;
3612             }
3613
3614             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3615                 looking_at(buf, &i, "<b1> ")) {
3616                 if (oldi > next_out) {
3617                     SendToPlayer(&buf[next_out], oldi - next_out);
3618                 }
3619                 next_out = i;
3620                 started = STARTED_HOLDINGS;
3621                 parse_pos = 0;
3622                 continue;
3623             }
3624
3625             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3626                 loggedOn = TRUE;
3627                 /* Header for a move list -- first line */
3628
3629                 switch (ics_getting_history) {
3630                   case H_FALSE:
3631                     switch (gameMode) {
3632                       case IcsIdle:
3633                       case BeginningOfGame:
3634                         /* User typed "moves" or "oldmoves" while we
3635                            were idle.  Pretend we asked for these
3636                            moves and soak them up so user can step
3637                            through them and/or save them.
3638                            */
3639                         Reset(FALSE, TRUE);
3640                         gameMode = IcsObserving;
3641                         ModeHighlight();
3642                         ics_gamenum = -1;
3643                         ics_getting_history = H_GOT_UNREQ_HEADER;
3644                         break;
3645                       case EditGame: /*?*/
3646                       case EditPosition: /*?*/
3647                         /* Should above feature work in these modes too? */
3648                         /* For now it doesn't */
3649                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3650                         break;
3651                       default:
3652                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3653                         break;
3654                     }
3655                     break;
3656                   case H_REQUESTED:
3657                     /* Is this the right one? */
3658                     if (gameInfo.white && gameInfo.black &&
3659                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3660                         strcmp(gameInfo.black, star_match[2]) == 0) {
3661                         /* All is well */
3662                         ics_getting_history = H_GOT_REQ_HEADER;
3663                     }
3664                     break;
3665                   case H_GOT_REQ_HEADER:
3666                   case H_GOT_UNREQ_HEADER:
3667                   case H_GOT_UNWANTED_HEADER:
3668                   case H_GETTING_MOVES:
3669                     /* Should not happen */
3670                     DisplayError(_("Error gathering move list: two headers"), 0);
3671                     ics_getting_history = H_FALSE;
3672                     break;
3673                 }
3674
3675                 /* Save player ratings into gameInfo if needed */
3676                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3677                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3678                     (gameInfo.whiteRating == -1 ||
3679                      gameInfo.blackRating == -1)) {
3680
3681                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3682                     gameInfo.blackRating = string_to_rating(star_match[3]);
3683                     if (appData.debugMode)
3684                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3685                               gameInfo.whiteRating, gameInfo.blackRating);
3686                 }
3687                 continue;
3688             }
3689
3690             if (looking_at(buf, &i,
3691               "* * match, initial time: * minute*, increment: * second")) {
3692                 /* Header for a move list -- second line */
3693                 /* Initial board will follow if this is a wild game */
3694                 if (gameInfo.event != NULL) free(gameInfo.event);
3695                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3696                 gameInfo.event = StrSave(str);
3697                 /* [HGM] we switched variant. Translate boards if needed. */
3698                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3699                 continue;
3700             }
3701
3702             if (looking_at(buf, &i, "Move  ")) {
3703                 /* Beginning of a move list */
3704                 switch (ics_getting_history) {
3705                   case H_FALSE:
3706                     /* Normally should not happen */
3707                     /* Maybe user hit reset while we were parsing */
3708                     break;
3709                   case H_REQUESTED:
3710                     /* Happens if we are ignoring a move list that is not
3711                      * the one we just requested.  Common if the user
3712                      * tries to observe two games without turning off
3713                      * getMoveList */
3714                     break;
3715                   case H_GETTING_MOVES:
3716                     /* Should not happen */
3717                     DisplayError(_("Error gathering move list: nested"), 0);
3718                     ics_getting_history = H_FALSE;
3719                     break;
3720                   case H_GOT_REQ_HEADER:
3721                     ics_getting_history = H_GETTING_MOVES;
3722                     started = STARTED_MOVES;
3723                     parse_pos = 0;
3724                     if (oldi > next_out) {
3725                         SendToPlayer(&buf[next_out], oldi - next_out);
3726                     }
3727                     break;
3728                   case H_GOT_UNREQ_HEADER:
3729                     ics_getting_history = H_GETTING_MOVES;
3730                     started = STARTED_MOVES_NOHIDE;
3731                     parse_pos = 0;
3732                     break;
3733                   case H_GOT_UNWANTED_HEADER:
3734                     ics_getting_history = H_FALSE;
3735                     break;
3736                 }
3737                 continue;
3738             }
3739
3740             if (looking_at(buf, &i, "% ") ||
3741                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3742                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3743                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3744                     soughtPending = FALSE;
3745                     seekGraphUp = TRUE;
3746                     DrawSeekGraph();
3747                 }
3748                 if(suppressKibitz) next_out = i;
3749                 savingComment = FALSE;
3750                 suppressKibitz = 0;
3751                 switch (started) {
3752                   case STARTED_MOVES:
3753                   case STARTED_MOVES_NOHIDE:
3754                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3755                     parse[parse_pos + i - oldi] = NULLCHAR;
3756                     ParseGameHistory(parse);
3757 #if ZIPPY
3758                     if (appData.zippyPlay && first.initDone) {
3759                         FeedMovesToProgram(&first, forwardMostMove);
3760                         if (gameMode == IcsPlayingWhite) {
3761                             if (WhiteOnMove(forwardMostMove)) {
3762                                 if (first.sendTime) {
3763                                   if (first.useColors) {
3764                                     SendToProgram("black\n", &first);
3765                                   }
3766                                   SendTimeRemaining(&first, TRUE);
3767                                 }
3768                                 if (first.useColors) {
3769                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3770                                 }
3771                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3772                                 first.maybeThinking = TRUE;
3773                             } else {
3774                                 if (first.usePlayother) {
3775                                   if (first.sendTime) {
3776                                     SendTimeRemaining(&first, TRUE);
3777                                   }
3778                                   SendToProgram("playother\n", &first);
3779                                   firstMove = FALSE;
3780                                 } else {
3781                                   firstMove = TRUE;
3782                                 }
3783                             }
3784                         } else if (gameMode == IcsPlayingBlack) {
3785                             if (!WhiteOnMove(forwardMostMove)) {
3786                                 if (first.sendTime) {
3787                                   if (first.useColors) {
3788                                     SendToProgram("white\n", &first);
3789                                   }
3790                                   SendTimeRemaining(&first, FALSE);
3791                                 }
3792                                 if (first.useColors) {
3793                                   SendToProgram("black\n", &first);
3794                                 }
3795                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3796                                 first.maybeThinking = TRUE;
3797                             } else {
3798                                 if (first.usePlayother) {
3799                                   if (first.sendTime) {
3800                                     SendTimeRemaining(&first, FALSE);
3801                                   }
3802                                   SendToProgram("playother\n", &first);
3803                                   firstMove = FALSE;
3804                                 } else {
3805                                   firstMove = TRUE;
3806                                 }
3807                             }
3808                         }
3809                     }
3810 #endif
3811                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3812                         /* Moves came from oldmoves or moves command
3813                            while we weren't doing anything else.
3814                            */
3815                         currentMove = forwardMostMove;
3816                         ClearHighlights();/*!!could figure this out*/
3817                         flipView = appData.flipView;
3818                         DrawPosition(TRUE, boards[currentMove]);
3819                         DisplayBothClocks();
3820                         snprintf(str, MSG_SIZ, "%s %s %s",
3821                                 gameInfo.white, _("vs."),  gameInfo.black);
3822                         DisplayTitle(str);
3823                         gameMode = IcsIdle;
3824                     } else {
3825                         /* Moves were history of an active game */
3826                         if (gameInfo.resultDetails != NULL) {
3827                             free(gameInfo.resultDetails);
3828                             gameInfo.resultDetails = NULL;
3829                         }
3830                     }
3831                     HistorySet(parseList, backwardMostMove,
3832                                forwardMostMove, currentMove-1);
3833                     DisplayMove(currentMove - 1);
3834                     if (started == STARTED_MOVES) next_out = i;
3835                     started = STARTED_NONE;
3836                     ics_getting_history = H_FALSE;
3837                     break;
3838
3839                   case STARTED_OBSERVE:
3840                     started = STARTED_NONE;
3841                     SendToICS(ics_prefix);
3842                     SendToICS("refresh\n");
3843                     break;
3844
3845                   default:
3846                     break;
3847                 }
3848                 if(bookHit) { // [HGM] book: simulate book reply
3849                     static char bookMove[MSG_SIZ]; // a bit generous?
3850
3851                     programStats.nodes = programStats.depth = programStats.time =
3852                     programStats.score = programStats.got_only_move = 0;
3853                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3854
3855                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3856                     strcat(bookMove, bookHit);
3857                     HandleMachineMove(bookMove, &first);
3858                 }
3859                 continue;
3860             }
3861
3862             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3863                  started == STARTED_HOLDINGS ||
3864                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3865                 /* Accumulate characters in move list or board */
3866                 parse[parse_pos++] = buf[i];
3867             }
3868
3869             /* Start of game messages.  Mostly we detect start of game
3870                when the first board image arrives.  On some versions
3871                of the ICS, though, we need to do a "refresh" after starting
3872                to observe in order to get the current board right away. */
3873             if (looking_at(buf, &i, "Adding game * to observation list")) {
3874                 started = STARTED_OBSERVE;
3875                 continue;
3876             }
3877
3878             /* Handle auto-observe */
3879             if (appData.autoObserve &&
3880                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3881                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3882                 char *player;
3883                 /* Choose the player that was highlighted, if any. */
3884                 if (star_match[0][0] == '\033' ||
3885                     star_match[1][0] != '\033') {
3886                     player = star_match[0];
3887                 } else {
3888                     player = star_match[2];
3889                 }
3890                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3891                         ics_prefix, StripHighlightAndTitle(player));
3892                 SendToICS(str);
3893
3894                 /* Save ratings from notify string */
3895                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3896                 player1Rating = string_to_rating(star_match[1]);
3897                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3898                 player2Rating = string_to_rating(star_match[3]);
3899
3900                 if (appData.debugMode)
3901                   fprintf(debugFP,
3902                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3903                           player1Name, player1Rating,
3904                           player2Name, player2Rating);
3905
3906                 continue;
3907             }
3908
3909             /* Deal with automatic examine mode after a game,
3910                and with IcsObserving -> IcsExamining transition */
3911             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3912                 looking_at(buf, &i, "has made you an examiner of game *")) {
3913
3914                 int gamenum = atoi(star_match[0]);
3915                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3916                     gamenum == ics_gamenum) {
3917                     /* We were already playing or observing this game;
3918                        no need to refetch history */
3919                     gameMode = IcsExamining;
3920                     if (pausing) {
3921                         pauseExamForwardMostMove = forwardMostMove;
3922                     } else if (currentMove < forwardMostMove) {
3923                         ForwardInner(forwardMostMove);
3924                     }
3925                 } else {
3926                     /* I don't think this case really can happen */
3927                     SendToICS(ics_prefix);
3928                     SendToICS("refresh\n");
3929                 }
3930                 continue;
3931             }
3932
3933             /* Error messages */
3934 //          if (ics_user_moved) {
3935             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3936                 if (looking_at(buf, &i, "Illegal move") ||
3937                     looking_at(buf, &i, "Not a legal move") ||
3938                     looking_at(buf, &i, "Your king is in check") ||
3939                     looking_at(buf, &i, "It isn't your turn") ||
3940                     looking_at(buf, &i, "It is not your move")) {
3941                     /* Illegal move */
3942                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3943                         currentMove = forwardMostMove-1;
3944                         DisplayMove(currentMove - 1); /* before DMError */
3945                         DrawPosition(FALSE, boards[currentMove]);
3946                         SwitchClocks(forwardMostMove-1); // [HGM] race
3947                         DisplayBothClocks();
3948                     }
3949                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3950                     ics_user_moved = 0;
3951                     continue;
3952                 }
3953             }
3954
3955             if (looking_at(buf, &i, "still have time") ||
3956                 looking_at(buf, &i, "not out of time") ||
3957                 looking_at(buf, &i, "either player is out of time") ||
3958                 looking_at(buf, &i, "has timeseal; checking")) {
3959                 /* We must have called his flag a little too soon */
3960                 whiteFlag = blackFlag = FALSE;
3961                 continue;
3962             }
3963
3964             if (looking_at(buf, &i, "added * seconds to") ||
3965                 looking_at(buf, &i, "seconds were added to")) {
3966                 /* Update the clocks */
3967                 SendToICS(ics_prefix);
3968                 SendToICS("refresh\n");
3969                 continue;
3970             }
3971
3972             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3973                 ics_clock_paused = TRUE;
3974                 StopClocks();
3975                 continue;
3976             }
3977
3978             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3979                 ics_clock_paused = FALSE;
3980                 StartClocks();
3981                 continue;
3982             }
3983
3984             /* Grab player ratings from the Creating: message.
3985                Note we have to check for the special case when
3986                the ICS inserts things like [white] or [black]. */
3987             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3988                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3989                 /* star_matches:
3990                    0    player 1 name (not necessarily white)
3991                    1    player 1 rating
3992                    2    empty, white, or black (IGNORED)
3993                    3    player 2 name (not necessarily black)
3994                    4    player 2 rating
3995
3996                    The names/ratings are sorted out when the game
3997                    actually starts (below).
3998                 */
3999                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4000                 player1Rating = string_to_rating(star_match[1]);
4001                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4002                 player2Rating = string_to_rating(star_match[4]);
4003
4004                 if (appData.debugMode)
4005                   fprintf(debugFP,
4006                           "Ratings from 'Creating:' %s %d, %s %d\n",
4007                           player1Name, player1Rating,
4008                           player2Name, player2Rating);
4009
4010                 continue;
4011             }
4012
4013             /* Improved generic start/end-of-game messages */
4014             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4015                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4016                 /* If tkind == 0: */
4017                 /* star_match[0] is the game number */
4018                 /*           [1] is the white player's name */
4019                 /*           [2] is the black player's name */
4020                 /* For end-of-game: */
4021                 /*           [3] is the reason for the game end */
4022                 /*           [4] is a PGN end game-token, preceded by " " */
4023                 /* For start-of-game: */
4024                 /*           [3] begins with "Creating" or "Continuing" */
4025                 /*           [4] is " *" or empty (don't care). */
4026                 int gamenum = atoi(star_match[0]);
4027                 char *whitename, *blackname, *why, *endtoken;
4028                 ChessMove endtype = EndOfFile;
4029
4030                 if (tkind == 0) {
4031                   whitename = star_match[1];
4032                   blackname = star_match[2];
4033                   why = star_match[3];
4034                   endtoken = star_match[4];
4035                 } else {
4036                   whitename = star_match[1];
4037                   blackname = star_match[3];
4038                   why = star_match[5];
4039                   endtoken = star_match[6];
4040                 }
4041
4042                 /* Game start messages */
4043                 if (strncmp(why, "Creating ", 9) == 0 ||
4044                     strncmp(why, "Continuing ", 11) == 0) {
4045                     gs_gamenum = gamenum;
4046                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4047                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4048                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4049 #if ZIPPY
4050                     if (appData.zippyPlay) {
4051                         ZippyGameStart(whitename, blackname);
4052                     }
4053 #endif /*ZIPPY*/
4054                     partnerBoardValid = FALSE; // [HGM] bughouse
4055                     continue;
4056                 }
4057
4058                 /* Game end messages */
4059                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4060                     ics_gamenum != gamenum) {
4061                     continue;
4062                 }
4063                 while (endtoken[0] == ' ') endtoken++;
4064                 switch (endtoken[0]) {
4065                   case '*':
4066                   default:
4067                     endtype = GameUnfinished;
4068                     break;
4069                   case '0':
4070                     endtype = BlackWins;
4071                     break;
4072                   case '1':
4073                     if (endtoken[1] == '/')
4074                       endtype = GameIsDrawn;
4075                     else
4076                       endtype = WhiteWins;
4077                     break;
4078                 }
4079                 GameEnds(endtype, why, GE_ICS);
4080 #if ZIPPY
4081                 if (appData.zippyPlay && first.initDone) {
4082                     ZippyGameEnd(endtype, why);
4083                     if (first.pr == NoProc) {
4084                       /* Start the next process early so that we'll
4085                          be ready for the next challenge */
4086                       StartChessProgram(&first);
4087                     }
4088                     /* Send "new" early, in case this command takes
4089                        a long time to finish, so that we'll be ready
4090                        for the next challenge. */
4091                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4092                     Reset(TRUE, TRUE);
4093                 }
4094 #endif /*ZIPPY*/
4095                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4096                 continue;
4097             }
4098
4099             if (looking_at(buf, &i, "Removing game * from observation") ||
4100                 looking_at(buf, &i, "no longer observing game *") ||
4101                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4102                 if (gameMode == IcsObserving &&
4103                     atoi(star_match[0]) == ics_gamenum)
4104                   {
4105                       /* icsEngineAnalyze */
4106                       if (appData.icsEngineAnalyze) {
4107                             ExitAnalyzeMode();
4108                             ModeHighlight();
4109                       }
4110                       StopClocks();
4111                       gameMode = IcsIdle;
4112                       ics_gamenum = -1;
4113                       ics_user_moved = FALSE;
4114                   }
4115                 continue;
4116             }
4117
4118             if (looking_at(buf, &i, "no longer examining game *")) {
4119                 if (gameMode == IcsExamining &&
4120                     atoi(star_match[0]) == ics_gamenum)
4121                   {
4122                       gameMode = IcsIdle;
4123                       ics_gamenum = -1;
4124                       ics_user_moved = FALSE;
4125                   }
4126                 continue;
4127             }
4128
4129             /* Advance leftover_start past any newlines we find,
4130                so only partial lines can get reparsed */
4131             if (looking_at(buf, &i, "\n")) {
4132                 prevColor = curColor;
4133                 if (curColor != ColorNormal) {
4134                     if (oldi > next_out) {
4135                         SendToPlayer(&buf[next_out], oldi - next_out);
4136                         next_out = oldi;
4137                     }
4138                     Colorize(ColorNormal, FALSE);
4139                     curColor = ColorNormal;
4140                 }
4141                 if (started == STARTED_BOARD) {
4142                     started = STARTED_NONE;
4143                     parse[parse_pos] = NULLCHAR;
4144                     ParseBoard12(parse);
4145                     ics_user_moved = 0;
4146
4147                     /* Send premove here */
4148                     if (appData.premove) {
4149                       char str[MSG_SIZ];
4150                       if (currentMove == 0 &&
4151                           gameMode == IcsPlayingWhite &&
4152                           appData.premoveWhite) {
4153                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4154                         if (appData.debugMode)
4155                           fprintf(debugFP, "Sending premove:\n");
4156                         SendToICS(str);
4157                       } else if (currentMove == 1 &&
4158                                  gameMode == IcsPlayingBlack &&
4159                                  appData.premoveBlack) {
4160                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4161                         if (appData.debugMode)
4162                           fprintf(debugFP, "Sending premove:\n");
4163                         SendToICS(str);
4164                       } else if (gotPremove) {
4165                         gotPremove = 0;
4166                         ClearPremoveHighlights();
4167                         if (appData.debugMode)
4168                           fprintf(debugFP, "Sending premove:\n");
4169                           UserMoveEvent(premoveFromX, premoveFromY,
4170                                         premoveToX, premoveToY,
4171                                         premovePromoChar);
4172                       }
4173                     }
4174
4175                     /* Usually suppress following prompt */
4176                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4177                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4178                         if (looking_at(buf, &i, "*% ")) {
4179                             savingComment = FALSE;
4180                             suppressKibitz = 0;
4181                         }
4182                     }
4183                     next_out = i;
4184                 } else if (started == STARTED_HOLDINGS) {
4185                     int gamenum;
4186                     char new_piece[MSG_SIZ];
4187                     started = STARTED_NONE;
4188                     parse[parse_pos] = NULLCHAR;
4189                     if (appData.debugMode)
4190                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4191                                                         parse, currentMove);
4192                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4193                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4194                         if (gameInfo.variant == VariantNormal) {
4195                           /* [HGM] We seem to switch variant during a game!
4196                            * Presumably no holdings were displayed, so we have
4197                            * to move the position two files to the right to
4198                            * create room for them!
4199                            */
4200                           VariantClass newVariant;
4201                           switch(gameInfo.boardWidth) { // base guess on board width
4202                                 case 9:  newVariant = VariantShogi; break;
4203                                 case 10: newVariant = VariantGreat; break;
4204                                 default: newVariant = VariantCrazyhouse; break;
4205                           }
4206                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4207                           /* Get a move list just to see the header, which
4208                              will tell us whether this is really bug or zh */
4209                           if (ics_getting_history == H_FALSE) {
4210                             ics_getting_history = H_REQUESTED;
4211                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4212                             SendToICS(str);
4213                           }
4214                         }
4215                         new_piece[0] = NULLCHAR;
4216                         sscanf(parse, "game %d white [%s black [%s <- %s",
4217                                &gamenum, white_holding, black_holding,
4218                                new_piece);
4219                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4220                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4221                         /* [HGM] copy holdings to board holdings area */
4222                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4223                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4224                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4225 #if ZIPPY
4226                         if (appData.zippyPlay && first.initDone) {
4227                             ZippyHoldings(white_holding, black_holding,
4228                                           new_piece);
4229                         }
4230 #endif /*ZIPPY*/
4231                         if (tinyLayout || smallLayout) {
4232                             char wh[16], bh[16];
4233                             PackHolding(wh, white_holding);
4234                             PackHolding(bh, black_holding);
4235                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4236                                     gameInfo.white, gameInfo.black);
4237                         } else {
4238                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4239                                     gameInfo.white, white_holding, _("vs."),
4240                                     gameInfo.black, black_holding);
4241                         }
4242                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4243                         DrawPosition(FALSE, boards[currentMove]);
4244                         DisplayTitle(str);
4245                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4246                         sscanf(parse, "game %d white [%s black [%s <- %s",
4247                                &gamenum, white_holding, black_holding,
4248                                new_piece);
4249                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4250                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4251                         /* [HGM] copy holdings to partner-board holdings area */
4252                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4253                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4254                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4255                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4256                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4257                       }
4258                     }
4259                     /* Suppress following prompt */
4260                     if (looking_at(buf, &i, "*% ")) {
4261                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4262                         savingComment = FALSE;
4263                         suppressKibitz = 0;
4264                     }
4265                     next_out = i;
4266                 }
4267                 continue;
4268             }
4269
4270             i++;                /* skip unparsed character and loop back */
4271         }
4272
4273         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4274 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4275 //          SendToPlayer(&buf[next_out], i - next_out);
4276             started != STARTED_HOLDINGS && leftover_start > next_out) {
4277             SendToPlayer(&buf[next_out], leftover_start - next_out);
4278             next_out = i;
4279         }
4280
4281         leftover_len = buf_len - leftover_start;
4282         /* if buffer ends with something we couldn't parse,
4283            reparse it after appending the next read */
4284
4285     } else if (count == 0) {
4286         RemoveInputSource(isr);
4287         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4288     } else {
4289         DisplayFatalError(_("Error reading from ICS"), error, 1);
4290     }
4291 }
4292
4293
4294 /* Board style 12 looks like this:
4295
4296    <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
4297
4298  * The "<12> " is stripped before it gets to this routine.  The two
4299  * trailing 0's (flip state and clock ticking) are later addition, and
4300  * some chess servers may not have them, or may have only the first.
4301  * Additional trailing fields may be added in the future.
4302  */
4303
4304 #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"
4305
4306 #define RELATION_OBSERVING_PLAYED    0
4307 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4308 #define RELATION_PLAYING_MYMOVE      1
4309 #define RELATION_PLAYING_NOTMYMOVE  -1
4310 #define RELATION_EXAMINING           2
4311 #define RELATION_ISOLATED_BOARD     -3
4312 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4313
4314 void
4315 ParseBoard12 (char *string)
4316 {
4317 #if ZIPPY
4318     int i, takeback;
4319     char *bookHit = NULL; // [HGM] book
4320 #endif
4321     GameMode newGameMode;
4322     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4323     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4324     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4325     char to_play, board_chars[200];
4326     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4327     char black[32], white[32];
4328     Board board;
4329     int prevMove = currentMove;
4330     int ticking = 2;
4331     ChessMove moveType;
4332     int fromX, fromY, toX, toY;
4333     char promoChar;
4334     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4335     Boolean weird = FALSE, reqFlag = FALSE;
4336
4337     fromX = fromY = toX = toY = -1;
4338
4339     newGame = FALSE;
4340
4341     if (appData.debugMode)
4342       fprintf(debugFP, "Parsing board: %s\n", string);
4343
4344     move_str[0] = NULLCHAR;
4345     elapsed_time[0] = NULLCHAR;
4346     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4347         int  i = 0, j;
4348         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4349             if(string[i] == ' ') { ranks++; files = 0; }
4350             else files++;
4351             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4352             i++;
4353         }
4354         for(j = 0; j <i; j++) board_chars[j] = string[j];
4355         board_chars[i] = '\0';
4356         string += i + 1;
4357     }
4358     n = sscanf(string, PATTERN, &to_play, &double_push,
4359                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4360                &gamenum, white, black, &relation, &basetime, &increment,
4361                &white_stren, &black_stren, &white_time, &black_time,
4362                &moveNum, str, elapsed_time, move_str, &ics_flip,
4363                &ticking);
4364
4365     if (n < 21) {
4366         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4367         DisplayError(str, 0);
4368         return;
4369     }
4370
4371     /* Convert the move number to internal form */
4372     moveNum = (moveNum - 1) * 2;
4373     if (to_play == 'B') moveNum++;
4374     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4375       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4376                         0, 1);
4377       return;
4378     }
4379
4380     switch (relation) {
4381       case RELATION_OBSERVING_PLAYED:
4382       case RELATION_OBSERVING_STATIC:
4383         if (gamenum == -1) {
4384             /* Old ICC buglet */
4385             relation = RELATION_OBSERVING_STATIC;
4386         }
4387         newGameMode = IcsObserving;
4388         break;
4389       case RELATION_PLAYING_MYMOVE:
4390       case RELATION_PLAYING_NOTMYMOVE:
4391         newGameMode =
4392           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4393             IcsPlayingWhite : IcsPlayingBlack;
4394         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4395         break;
4396       case RELATION_EXAMINING:
4397         newGameMode = IcsExamining;
4398         break;
4399       case RELATION_ISOLATED_BOARD:
4400       default:
4401         /* Just display this board.  If user was doing something else,
4402            we will forget about it until the next board comes. */
4403         newGameMode = IcsIdle;
4404         break;
4405       case RELATION_STARTING_POSITION:
4406         newGameMode = gameMode;
4407         break;
4408     }
4409
4410     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4411         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4412          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4413       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4414       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4415       static int lastBgGame = -1;
4416       char *toSqr;
4417       for (k = 0; k < ranks; k++) {
4418         for (j = 0; j < files; j++)
4419           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4420         if(gameInfo.holdingsWidth > 1) {
4421              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4422              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4423         }
4424       }
4425       CopyBoard(partnerBoard, board);
4426       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4427         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4428         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4429       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4430       if(toSqr = strchr(str, '-')) {
4431         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4432         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4433       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4434       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4435       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4436       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4437       if(twoBoards) {
4438           DisplayWhiteClock(white_time*fac, to_play == 'W');
4439           DisplayBlackClock(black_time*fac, to_play != 'W');
4440           activePartner = to_play;
4441           if(gamenum != lastBgGame) {
4442               char buf[MSG_SIZ];
4443               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4444               DisplayTitle(buf);
4445           }
4446           lastBgGame = gamenum;
4447           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4448                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4449       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4450                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4451       if(!twoBoards) DisplayMessage(partnerStatus, "");
4452         partnerBoardValid = TRUE;
4453       return;
4454     }
4455
4456     if(appData.dualBoard && appData.bgObserve) {
4457         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4458             SendToICS(ics_prefix), SendToICS("pobserve\n");
4459         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4460             char buf[MSG_SIZ];
4461             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4462             SendToICS(buf);
4463         }
4464     }
4465
4466     /* Modify behavior for initial board display on move listing
4467        of wild games.
4468        */
4469     switch (ics_getting_history) {
4470       case H_FALSE:
4471       case H_REQUESTED:
4472         break;
4473       case H_GOT_REQ_HEADER:
4474       case H_GOT_UNREQ_HEADER:
4475         /* This is the initial position of the current game */
4476         gamenum = ics_gamenum;
4477         moveNum = 0;            /* old ICS bug workaround */
4478         if (to_play == 'B') {
4479           startedFromSetupPosition = TRUE;
4480           blackPlaysFirst = TRUE;
4481           moveNum = 1;
4482           if (forwardMostMove == 0) forwardMostMove = 1;
4483           if (backwardMostMove == 0) backwardMostMove = 1;
4484           if (currentMove == 0) currentMove = 1;
4485         }
4486         newGameMode = gameMode;
4487         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4488         break;
4489       case H_GOT_UNWANTED_HEADER:
4490         /* This is an initial board that we don't want */
4491         return;
4492       case H_GETTING_MOVES:
4493         /* Should not happen */
4494         DisplayError(_("Error gathering move list: extra board"), 0);
4495         ics_getting_history = H_FALSE;
4496         return;
4497     }
4498
4499    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4500                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4501                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4502      /* [HGM] We seem to have switched variant unexpectedly
4503       * Try to guess new variant from board size
4504       */
4505           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4506           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4507           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4508           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4509           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4510           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4511           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4512           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4513           /* Get a move list just to see the header, which
4514              will tell us whether this is really bug or zh */
4515           if (ics_getting_history == H_FALSE) {
4516             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4517             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4518             SendToICS(str);
4519           }
4520     }
4521
4522     /* Take action if this is the first board of a new game, or of a
4523        different game than is currently being displayed.  */
4524     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4525         relation == RELATION_ISOLATED_BOARD) {
4526
4527         /* Forget the old game and get the history (if any) of the new one */
4528         if (gameMode != BeginningOfGame) {
4529           Reset(TRUE, TRUE);
4530         }
4531         newGame = TRUE;
4532         if (appData.autoRaiseBoard) BoardToTop();
4533         prevMove = -3;
4534         if (gamenum == -1) {
4535             newGameMode = IcsIdle;
4536         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4537                    appData.getMoveList && !reqFlag) {
4538             /* Need to get game history */
4539             ics_getting_history = H_REQUESTED;
4540             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4541             SendToICS(str);
4542         }
4543
4544         /* Initially flip the board to have black on the bottom if playing
4545            black or if the ICS flip flag is set, but let the user change
4546            it with the Flip View button. */
4547         flipView = appData.autoFlipView ?
4548           (newGameMode == IcsPlayingBlack) || ics_flip :
4549           appData.flipView;
4550
4551         /* Done with values from previous mode; copy in new ones */
4552         gameMode = newGameMode;
4553         ModeHighlight();
4554         ics_gamenum = gamenum;
4555         if (gamenum == gs_gamenum) {
4556             int klen = strlen(gs_kind);
4557             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4558             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4559             gameInfo.event = StrSave(str);
4560         } else {
4561             gameInfo.event = StrSave("ICS game");
4562         }
4563         gameInfo.site = StrSave(appData.icsHost);
4564         gameInfo.date = PGNDate();
4565         gameInfo.round = StrSave("-");
4566         gameInfo.white = StrSave(white);
4567         gameInfo.black = StrSave(black);
4568         timeControl = basetime * 60 * 1000;
4569         timeControl_2 = 0;
4570         timeIncrement = increment * 1000;
4571         movesPerSession = 0;
4572         gameInfo.timeControl = TimeControlTagValue();
4573         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4574   if (appData.debugMode) {
4575     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4576     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4577     setbuf(debugFP, NULL);
4578   }
4579
4580         gameInfo.outOfBook = NULL;
4581
4582         /* Do we have the ratings? */
4583         if (strcmp(player1Name, white) == 0 &&
4584             strcmp(player2Name, black) == 0) {
4585             if (appData.debugMode)
4586               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4587                       player1Rating, player2Rating);
4588             gameInfo.whiteRating = player1Rating;
4589             gameInfo.blackRating = player2Rating;
4590         } else if (strcmp(player2Name, white) == 0 &&
4591                    strcmp(player1Name, black) == 0) {
4592             if (appData.debugMode)
4593               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4594                       player2Rating, player1Rating);
4595             gameInfo.whiteRating = player2Rating;
4596             gameInfo.blackRating = player1Rating;
4597         }
4598         player1Name[0] = player2Name[0] = NULLCHAR;
4599
4600         /* Silence shouts if requested */
4601         if (appData.quietPlay &&
4602             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4603             SendToICS(ics_prefix);
4604             SendToICS("set shout 0\n");
4605         }
4606     }
4607
4608     /* Deal with midgame name changes */
4609     if (!newGame) {
4610         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4611             if (gameInfo.white) free(gameInfo.white);
4612             gameInfo.white = StrSave(white);
4613         }
4614         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4615             if (gameInfo.black) free(gameInfo.black);
4616             gameInfo.black = StrSave(black);
4617         }
4618     }
4619
4620     /* Throw away game result if anything actually changes in examine mode */
4621     if (gameMode == IcsExamining && !newGame) {
4622         gameInfo.result = GameUnfinished;
4623         if (gameInfo.resultDetails != NULL) {
4624             free(gameInfo.resultDetails);
4625             gameInfo.resultDetails = NULL;
4626         }
4627     }
4628
4629     /* In pausing && IcsExamining mode, we ignore boards coming
4630        in if they are in a different variation than we are. */
4631     if (pauseExamInvalid) return;
4632     if (pausing && gameMode == IcsExamining) {
4633         if (moveNum <= pauseExamForwardMostMove) {
4634             pauseExamInvalid = TRUE;
4635             forwardMostMove = pauseExamForwardMostMove;
4636             return;
4637         }
4638     }
4639
4640   if (appData.debugMode) {
4641     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4642   }
4643     /* Parse the board */
4644     for (k = 0; k < ranks; k++) {
4645       for (j = 0; j < files; j++)
4646         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4647       if(gameInfo.holdingsWidth > 1) {
4648            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4649            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4650       }
4651     }
4652     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4653       board[5][BOARD_RGHT+1] = WhiteAngel;
4654       board[6][BOARD_RGHT+1] = WhiteMarshall;
4655       board[1][0] = BlackMarshall;
4656       board[2][0] = BlackAngel;
4657       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4658     }
4659     CopyBoard(boards[moveNum], board);
4660     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4661     if (moveNum == 0) {
4662         startedFromSetupPosition =
4663           !CompareBoards(board, initialPosition);
4664         if(startedFromSetupPosition)
4665             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4666     }
4667
4668     /* [HGM] Set castling rights. Take the outermost Rooks,
4669        to make it also work for FRC opening positions. Note that board12
4670        is really defective for later FRC positions, as it has no way to
4671        indicate which Rook can castle if they are on the same side of King.
4672        For the initial position we grant rights to the outermost Rooks,
4673        and remember thos rights, and we then copy them on positions
4674        later in an FRC game. This means WB might not recognize castlings with
4675        Rooks that have moved back to their original position as illegal,
4676        but in ICS mode that is not its job anyway.
4677     */
4678     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4679     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4680
4681         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4682             if(board[0][i] == WhiteRook) j = i;
4683         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4684         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4685             if(board[0][i] == WhiteRook) j = i;
4686         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4687         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4688             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4689         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4690         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4691             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4692         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4693
4694         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4695         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4696         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4697             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4698         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4699             if(board[BOARD_HEIGHT-1][k] == bKing)
4700                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4701         if(gameInfo.variant == VariantTwoKings) {
4702             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4703             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4704             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4705         }
4706     } else { int r;
4707         r = boards[moveNum][CASTLING][0] = initialRights[0];
4708         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4709         r = boards[moveNum][CASTLING][1] = initialRights[1];
4710         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4711         r = boards[moveNum][CASTLING][3] = initialRights[3];
4712         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4713         r = boards[moveNum][CASTLING][4] = initialRights[4];
4714         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4715         /* wildcastle kludge: always assume King has rights */
4716         r = boards[moveNum][CASTLING][2] = initialRights[2];
4717         r = boards[moveNum][CASTLING][5] = initialRights[5];
4718     }
4719     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4720     boards[moveNum][EP_STATUS] = EP_NONE;
4721     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4722     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4723     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4724
4725
4726     if (ics_getting_history == H_GOT_REQ_HEADER ||
4727         ics_getting_history == H_GOT_UNREQ_HEADER) {
4728         /* This was an initial position from a move list, not
4729            the current position */
4730         return;
4731     }
4732
4733     /* Update currentMove and known move number limits */
4734     newMove = newGame || moveNum > forwardMostMove;
4735
4736     if (newGame) {
4737         forwardMostMove = backwardMostMove = currentMove = moveNum;
4738         if (gameMode == IcsExamining && moveNum == 0) {
4739           /* Workaround for ICS limitation: we are not told the wild
4740              type when starting to examine a game.  But if we ask for
4741              the move list, the move list header will tell us */
4742             ics_getting_history = H_REQUESTED;
4743             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4744             SendToICS(str);
4745         }
4746     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4747                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4748 #if ZIPPY
4749         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4750         /* [HGM] applied this also to an engine that is silently watching        */
4751         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4752             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4753             gameInfo.variant == currentlyInitializedVariant) {
4754           takeback = forwardMostMove - moveNum;
4755           for (i = 0; i < takeback; i++) {
4756             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4757             SendToProgram("undo\n", &first);
4758           }
4759         }
4760 #endif
4761
4762         forwardMostMove = moveNum;
4763         if (!pausing || currentMove > forwardMostMove)
4764           currentMove = forwardMostMove;
4765     } else {
4766         /* New part of history that is not contiguous with old part */
4767         if (pausing && gameMode == IcsExamining) {
4768             pauseExamInvalid = TRUE;
4769             forwardMostMove = pauseExamForwardMostMove;
4770             return;
4771         }
4772         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4773 #if ZIPPY
4774             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4775                 // [HGM] when we will receive the move list we now request, it will be
4776                 // fed to the engine from the first move on. So if the engine is not
4777                 // in the initial position now, bring it there.
4778                 InitChessProgram(&first, 0);
4779             }
4780 #endif
4781             ics_getting_history = H_REQUESTED;
4782             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4783             SendToICS(str);
4784         }
4785         forwardMostMove = backwardMostMove = currentMove = moveNum;
4786     }
4787
4788     /* Update the clocks */
4789     if (strchr(elapsed_time, '.')) {
4790       /* Time is in ms */
4791       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4792       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4793     } else {
4794       /* Time is in seconds */
4795       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4796       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4797     }
4798
4799
4800 #if ZIPPY
4801     if (appData.zippyPlay && newGame &&
4802         gameMode != IcsObserving && gameMode != IcsIdle &&
4803         gameMode != IcsExamining)
4804       ZippyFirstBoard(moveNum, basetime, increment);
4805 #endif
4806
4807     /* Put the move on the move list, first converting
4808        to canonical algebraic form. */
4809     if (moveNum > 0) {
4810   if (appData.debugMode) {
4811     int f = forwardMostMove;
4812     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4813             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4814             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4815     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4816     fprintf(debugFP, "moveNum = %d\n", moveNum);
4817     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4818     setbuf(debugFP, NULL);
4819   }
4820         if (moveNum <= backwardMostMove) {
4821             /* We don't know what the board looked like before
4822                this move.  Punt. */
4823           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4824             strcat(parseList[moveNum - 1], " ");
4825             strcat(parseList[moveNum - 1], elapsed_time);
4826             moveList[moveNum - 1][0] = NULLCHAR;
4827         } else if (strcmp(move_str, "none") == 0) {
4828             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4829             /* Again, we don't know what the board looked like;
4830                this is really the start of the game. */
4831             parseList[moveNum - 1][0] = NULLCHAR;
4832             moveList[moveNum - 1][0] = NULLCHAR;
4833             backwardMostMove = moveNum;
4834             startedFromSetupPosition = TRUE;
4835             fromX = fromY = toX = toY = -1;
4836         } else {
4837           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4838           //                 So we parse the long-algebraic move string in stead of the SAN move
4839           int valid; char buf[MSG_SIZ], *prom;
4840
4841           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4842                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4843           // str looks something like "Q/a1-a2"; kill the slash
4844           if(str[1] == '/')
4845             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4846           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4847           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4848                 strcat(buf, prom); // long move lacks promo specification!
4849           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4850                 if(appData.debugMode)
4851                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4852                 safeStrCpy(move_str, buf, MSG_SIZ);
4853           }
4854           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4855                                 &fromX, &fromY, &toX, &toY, &promoChar)
4856                || ParseOneMove(buf, moveNum - 1, &moveType,
4857                                 &fromX, &fromY, &toX, &toY, &promoChar);
4858           // end of long SAN patch
4859           if (valid) {
4860             (void) CoordsToAlgebraic(boards[moveNum - 1],
4861                                      PosFlags(moveNum - 1),
4862                                      fromY, fromX, toY, toX, promoChar,
4863                                      parseList[moveNum-1]);
4864             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4865               case MT_NONE:
4866               case MT_STALEMATE:
4867               default:
4868                 break;
4869               case MT_CHECK:
4870                 if(!IS_SHOGI(gameInfo.variant))
4871                     strcat(parseList[moveNum - 1], "+");
4872                 break;
4873               case MT_CHECKMATE:
4874               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4875                 strcat(parseList[moveNum - 1], "#");
4876                 break;
4877             }
4878             strcat(parseList[moveNum - 1], " ");
4879             strcat(parseList[moveNum - 1], elapsed_time);
4880             /* currentMoveString is set as a side-effect of ParseOneMove */
4881             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4882             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4883             strcat(moveList[moveNum - 1], "\n");
4884
4885             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4886                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4887               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4888                 ChessSquare old, new = boards[moveNum][k][j];
4889                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4890                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4891                   if(old == new) continue;
4892                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4893                   else if(new == WhiteWazir || new == BlackWazir) {
4894                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4895                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4896                       else boards[moveNum][k][j] = old; // preserve type of Gold
4897                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4898                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4899               }
4900           } else {
4901             /* Move from ICS was illegal!?  Punt. */
4902             if (appData.debugMode) {
4903               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4904               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4905             }
4906             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4907             strcat(parseList[moveNum - 1], " ");
4908             strcat(parseList[moveNum - 1], elapsed_time);
4909             moveList[moveNum - 1][0] = NULLCHAR;
4910             fromX = fromY = toX = toY = -1;
4911           }
4912         }
4913   if (appData.debugMode) {
4914     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4915     setbuf(debugFP, NULL);
4916   }
4917
4918 #if ZIPPY
4919         /* Send move to chess program (BEFORE animating it). */
4920         if (appData.zippyPlay && !newGame && newMove &&
4921            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4922
4923             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4924                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4925                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4926                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4927                             move_str);
4928                     DisplayError(str, 0);
4929                 } else {
4930                     if (first.sendTime) {
4931                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4932                     }
4933                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4934                     if (firstMove && !bookHit) {
4935                         firstMove = FALSE;
4936                         if (first.useColors) {
4937                           SendToProgram(gameMode == IcsPlayingWhite ?
4938                                         "white\ngo\n" :
4939                                         "black\ngo\n", &first);
4940                         } else {
4941                           SendToProgram("go\n", &first);
4942                         }
4943                         first.maybeThinking = TRUE;
4944                     }
4945                 }
4946             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4947               if (moveList[moveNum - 1][0] == NULLCHAR) {
4948                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4949                 DisplayError(str, 0);
4950               } else {
4951                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4952                 SendMoveToProgram(moveNum - 1, &first);
4953               }
4954             }
4955         }
4956 #endif
4957     }
4958
4959     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4960         /* If move comes from a remote source, animate it.  If it
4961            isn't remote, it will have already been animated. */
4962         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4963             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4964         }
4965         if (!pausing && appData.highlightLastMove) {
4966             SetHighlights(fromX, fromY, toX, toY);
4967         }
4968     }
4969
4970     /* Start the clocks */
4971     whiteFlag = blackFlag = FALSE;
4972     appData.clockMode = !(basetime == 0 && increment == 0);
4973     if (ticking == 0) {
4974       ics_clock_paused = TRUE;
4975       StopClocks();
4976     } else if (ticking == 1) {
4977       ics_clock_paused = FALSE;
4978     }
4979     if (gameMode == IcsIdle ||
4980         relation == RELATION_OBSERVING_STATIC ||
4981         relation == RELATION_EXAMINING ||
4982         ics_clock_paused)
4983       DisplayBothClocks();
4984     else
4985       StartClocks();
4986
4987     /* Display opponents and material strengths */
4988     if (gameInfo.variant != VariantBughouse &&
4989         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4990         if (tinyLayout || smallLayout) {
4991             if(gameInfo.variant == VariantNormal)
4992               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4993                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4994                     basetime, increment);
4995             else
4996               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4997                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4998                     basetime, increment, (int) gameInfo.variant);
4999         } else {
5000             if(gameInfo.variant == VariantNormal)
5001               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5002                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5003                     basetime, increment);
5004             else
5005               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5006                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5007                     basetime, increment, VariantName(gameInfo.variant));
5008         }
5009         DisplayTitle(str);
5010   if (appData.debugMode) {
5011     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5012   }
5013     }
5014
5015
5016     /* Display the board */
5017     if (!pausing && !appData.noGUI) {
5018
5019       if (appData.premove)
5020           if (!gotPremove ||
5021              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5022              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5023               ClearPremoveHighlights();
5024
5025       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5026         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5027       DrawPosition(j, boards[currentMove]);
5028
5029       DisplayMove(moveNum - 1);
5030       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5031             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5032               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5033         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5034       }
5035     }
5036
5037     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5038 #if ZIPPY
5039     if(bookHit) { // [HGM] book: simulate book reply
5040         static char bookMove[MSG_SIZ]; // a bit generous?
5041
5042         programStats.nodes = programStats.depth = programStats.time =
5043         programStats.score = programStats.got_only_move = 0;
5044         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5045
5046         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5047         strcat(bookMove, bookHit);
5048         HandleMachineMove(bookMove, &first);
5049     }
5050 #endif
5051 }
5052
5053 void
5054 GetMoveListEvent ()
5055 {
5056     char buf[MSG_SIZ];
5057     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5058         ics_getting_history = H_REQUESTED;
5059         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5060         SendToICS(buf);
5061     }
5062 }
5063
5064 void
5065 SendToBoth (char *msg)
5066 {   // to make it easy to keep two engines in step in dual analysis
5067     SendToProgram(msg, &first);
5068     if(second.analyzing) SendToProgram(msg, &second);
5069 }
5070
5071 void
5072 AnalysisPeriodicEvent (int force)
5073 {
5074     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5075          && !force) || !appData.periodicUpdates)
5076       return;
5077
5078     /* Send . command to Crafty to collect stats */
5079     SendToBoth(".\n");
5080
5081     /* Don't send another until we get a response (this makes
5082        us stop sending to old Crafty's which don't understand
5083        the "." command (sending illegal cmds resets node count & time,
5084        which looks bad)) */
5085     programStats.ok_to_send = 0;
5086 }
5087
5088 void
5089 ics_update_width (int new_width)
5090 {
5091         ics_printf("set width %d\n", new_width);
5092 }
5093
5094 void
5095 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5096 {
5097     char buf[MSG_SIZ];
5098
5099     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5100         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5101             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5102             SendToProgram(buf, cps);
5103             return;
5104         }
5105         // null move in variant where engine does not understand it (for analysis purposes)
5106         SendBoard(cps, moveNum + 1); // send position after move in stead.
5107         return;
5108     }
5109     if (cps->useUsermove) {
5110       SendToProgram("usermove ", cps);
5111     }
5112     if (cps->useSAN) {
5113       char *space;
5114       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5115         int len = space - parseList[moveNum];
5116         memcpy(buf, parseList[moveNum], len);
5117         buf[len++] = '\n';
5118         buf[len] = NULLCHAR;
5119       } else {
5120         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5121       }
5122       SendToProgram(buf, cps);
5123     } else {
5124       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5125         AlphaRank(moveList[moveNum], 4);
5126         SendToProgram(moveList[moveNum], cps);
5127         AlphaRank(moveList[moveNum], 4); // and back
5128       } else
5129       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5130        * the engine. It would be nice to have a better way to identify castle
5131        * moves here. */
5132       if(appData.fischerCastling && cps->useOOCastle) {
5133         int fromX = moveList[moveNum][0] - AAA;
5134         int fromY = moveList[moveNum][1] - ONE;
5135         int toX = moveList[moveNum][2] - AAA;
5136         int toY = moveList[moveNum][3] - ONE;
5137         if((boards[moveNum][fromY][fromX] == WhiteKing
5138             && boards[moveNum][toY][toX] == WhiteRook)
5139            || (boards[moveNum][fromY][fromX] == BlackKing
5140                && boards[moveNum][toY][toX] == BlackRook)) {
5141           if(toX > fromX) SendToProgram("O-O\n", cps);
5142           else SendToProgram("O-O-O\n", cps);
5143         }
5144         else SendToProgram(moveList[moveNum], cps);
5145       } else
5146       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5147         char *m = moveList[moveNum];
5148         if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
5149           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5150                                                m[2], m[3] - '0',
5151                                                m[5], m[6] - '0',
5152                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5153         else
5154           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5155                                                m[5], m[6] - '0',
5156                                                m[5], m[6] - '0',
5157                                                m[2], m[3] - '0');
5158           SendToProgram(buf, cps);
5159       } else
5160       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5161         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5162           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5163           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5164                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5165         } else
5166           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5167                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5168         SendToProgram(buf, cps);
5169       }
5170       else SendToProgram(moveList[moveNum], cps);
5171       /* End of additions by Tord */
5172     }
5173
5174     /* [HGM] setting up the opening has brought engine in force mode! */
5175     /*       Send 'go' if we are in a mode where machine should play. */
5176     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5177         (gameMode == TwoMachinesPlay   ||
5178 #if ZIPPY
5179          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5180 #endif
5181          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5182         SendToProgram("go\n", cps);
5183   if (appData.debugMode) {
5184     fprintf(debugFP, "(extra)\n");
5185   }
5186     }
5187     setboardSpoiledMachineBlack = 0;
5188 }
5189
5190 void
5191 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5192 {
5193     char user_move[MSG_SIZ];
5194     char suffix[4];
5195
5196     if(gameInfo.variant == VariantSChess && promoChar) {
5197         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5198         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5199     } else suffix[0] = NULLCHAR;
5200
5201     switch (moveType) {
5202       default:
5203         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5204                 (int)moveType, fromX, fromY, toX, toY);
5205         DisplayError(user_move + strlen("say "), 0);
5206         break;
5207       case WhiteKingSideCastle:
5208       case BlackKingSideCastle:
5209       case WhiteQueenSideCastleWild:
5210       case BlackQueenSideCastleWild:
5211       /* PUSH Fabien */
5212       case WhiteHSideCastleFR:
5213       case BlackHSideCastleFR:
5214       /* POP Fabien */
5215         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5216         break;
5217       case WhiteQueenSideCastle:
5218       case BlackQueenSideCastle:
5219       case WhiteKingSideCastleWild:
5220       case BlackKingSideCastleWild:
5221       /* PUSH Fabien */
5222       case WhiteASideCastleFR:
5223       case BlackASideCastleFR:
5224       /* POP Fabien */
5225         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5226         break;
5227       case WhiteNonPromotion:
5228       case BlackNonPromotion:
5229         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5230         break;
5231       case WhitePromotion:
5232       case BlackPromotion:
5233         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5234            gameInfo.variant == VariantMakruk)
5235           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5236                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5237                 PieceToChar(WhiteFerz));
5238         else if(gameInfo.variant == VariantGreat)
5239           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5240                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5241                 PieceToChar(WhiteMan));
5242         else
5243           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5244                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5245                 promoChar);
5246         break;
5247       case WhiteDrop:
5248       case BlackDrop:
5249       drop:
5250         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5251                  ToUpper(PieceToChar((ChessSquare) fromX)),
5252                  AAA + toX, ONE + toY);
5253         break;
5254       case IllegalMove:  /* could be a variant we don't quite understand */
5255         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5256       case NormalMove:
5257       case WhiteCapturesEnPassant:
5258       case BlackCapturesEnPassant:
5259         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5260                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5261         break;
5262     }
5263     SendToICS(user_move);
5264     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5265         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5266 }
5267
5268 void
5269 UploadGameEvent ()
5270 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5271     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5272     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5273     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5274       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5275       return;
5276     }
5277     if(gameMode != IcsExamining) { // is this ever not the case?
5278         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5279
5280         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5281           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5282         } else { // on FICS we must first go to general examine mode
5283           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5284         }
5285         if(gameInfo.variant != VariantNormal) {
5286             // try figure out wild number, as xboard names are not always valid on ICS
5287             for(i=1; i<=36; i++) {
5288               snprintf(buf, MSG_SIZ, "wild/%d", i);
5289                 if(StringToVariant(buf) == gameInfo.variant) break;
5290             }
5291             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5292             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5293             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5294         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5295         SendToICS(ics_prefix);
5296         SendToICS(buf);
5297         if(startedFromSetupPosition || backwardMostMove != 0) {
5298           fen = PositionToFEN(backwardMostMove, NULL, 1);
5299           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5300             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5301             SendToICS(buf);
5302           } else { // FICS: everything has to set by separate bsetup commands
5303             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5304             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5305             SendToICS(buf);
5306             if(!WhiteOnMove(backwardMostMove)) {
5307                 SendToICS("bsetup tomove black\n");
5308             }
5309             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5310             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5311             SendToICS(buf);
5312             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5313             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5314             SendToICS(buf);
5315             i = boards[backwardMostMove][EP_STATUS];
5316             if(i >= 0) { // set e.p.
5317               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5318                 SendToICS(buf);
5319             }
5320             bsetup++;
5321           }
5322         }
5323       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5324             SendToICS("bsetup done\n"); // switch to normal examining.
5325     }
5326     for(i = backwardMostMove; i<last; i++) {
5327         char buf[20];
5328         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5329         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5330             int len = strlen(moveList[i]);
5331             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5332             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5333         }
5334         SendToICS(buf);
5335     }
5336     SendToICS(ics_prefix);
5337     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5338 }
5339
5340 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5341 int legNr = 1;
5342
5343 void
5344 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5345 {
5346     if (rf == DROP_RANK) {
5347       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5348       sprintf(move, "%c@%c%c\n",
5349                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5350     } else {
5351         if (promoChar == 'x' || promoChar == NULLCHAR) {
5352           sprintf(move, "%c%c%c%c\n",
5353                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5354           if(killX >= 0 && killY >= 0) {
5355             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5356             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + killX, ONE + killY);
5357           }
5358         } else {
5359             sprintf(move, "%c%c%c%c%c\n",
5360                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5361         }
5362     }
5363 }
5364
5365 void
5366 ProcessICSInitScript (FILE *f)
5367 {
5368     char buf[MSG_SIZ];
5369
5370     while (fgets(buf, MSG_SIZ, f)) {
5371         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5372     }
5373
5374     fclose(f);
5375 }
5376
5377
5378 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5379 int dragging;
5380 static ClickType lastClickType;
5381
5382 int
5383 PieceInString (char *s, ChessSquare piece)
5384 {
5385   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5386   while((p = strchr(s, ID))) {
5387     if(!suffix || p[1] == suffix) return TRUE;
5388     s = p;
5389   }
5390   return FALSE;
5391 }
5392
5393 int
5394 Partner (ChessSquare *p)
5395 { // change piece into promotion partner if one shogi-promotes to the other
5396   ChessSquare partner = promoPartner[*p];
5397   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5398   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5399   *p = partner;
5400   return 1;
5401 }
5402
5403 void
5404 Sweep (int step)
5405 {
5406     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5407     static int toggleFlag;
5408     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5409     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5410     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5411     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5412     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5413     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5414     do {
5415         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5416         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5417         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5418         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5419         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5420         if(!step) step = -1;
5421     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5422             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5423             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5424             promoSweep == pawn ||
5425             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5426             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5427     if(toX >= 0) {
5428         int victim = boards[currentMove][toY][toX];
5429         boards[currentMove][toY][toX] = promoSweep;
5430         DrawPosition(FALSE, boards[currentMove]);
5431         boards[currentMove][toY][toX] = victim;
5432     } else
5433     ChangeDragPiece(promoSweep);
5434 }
5435
5436 int
5437 PromoScroll (int x, int y)
5438 {
5439   int step = 0;
5440
5441   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5442   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5443   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5444   if(!step) return FALSE;
5445   lastX = x; lastY = y;
5446   if((promoSweep < BlackPawn) == flipView) step = -step;
5447   if(step > 0) selectFlag = 1;
5448   if(!selectFlag) Sweep(step);
5449   return FALSE;
5450 }
5451
5452 void
5453 NextPiece (int step)
5454 {
5455     ChessSquare piece = boards[currentMove][toY][toX];
5456     do {
5457         pieceSweep -= step;
5458         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5459         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5460         if(!step) step = -1;
5461     } while(PieceToChar(pieceSweep) == '.');
5462     boards[currentMove][toY][toX] = pieceSweep;
5463     DrawPosition(FALSE, boards[currentMove]);
5464     boards[currentMove][toY][toX] = piece;
5465 }
5466 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5467 void
5468 AlphaRank (char *move, int n)
5469 {
5470 //    char *p = move, c; int x, y;
5471
5472     if (appData.debugMode) {
5473         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5474     }
5475
5476     if(move[1]=='*' &&
5477        move[2]>='0' && move[2]<='9' &&
5478        move[3]>='a' && move[3]<='x'    ) {
5479         move[1] = '@';
5480         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5481         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5482     } else
5483     if(move[0]>='0' && move[0]<='9' &&
5484        move[1]>='a' && move[1]<='x' &&
5485        move[2]>='0' && move[2]<='9' &&
5486        move[3]>='a' && move[3]<='x'    ) {
5487         /* input move, Shogi -> normal */
5488         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5489         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5490         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5491         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5492     } else
5493     if(move[1]=='@' &&
5494        move[3]>='0' && move[3]<='9' &&
5495        move[2]>='a' && move[2]<='x'    ) {
5496         move[1] = '*';
5497         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5498         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5499     } else
5500     if(
5501        move[0]>='a' && move[0]<='x' &&
5502        move[3]>='0' && move[3]<='9' &&
5503        move[2]>='a' && move[2]<='x'    ) {
5504          /* output move, normal -> Shogi */
5505         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5506         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5507         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5508         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5509         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5510     }
5511     if (appData.debugMode) {
5512         fprintf(debugFP, "   out = '%s'\n", move);
5513     }
5514 }
5515
5516 char yy_textstr[8000];
5517
5518 /* Parser for moves from gnuchess, ICS, or user typein box */
5519 Boolean
5520 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5521 {
5522     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5523
5524     switch (*moveType) {
5525       case WhitePromotion:
5526       case BlackPromotion:
5527       case WhiteNonPromotion:
5528       case BlackNonPromotion:
5529       case NormalMove:
5530       case FirstLeg:
5531       case WhiteCapturesEnPassant:
5532       case BlackCapturesEnPassant:
5533       case WhiteKingSideCastle:
5534       case WhiteQueenSideCastle:
5535       case BlackKingSideCastle:
5536       case BlackQueenSideCastle:
5537       case WhiteKingSideCastleWild:
5538       case WhiteQueenSideCastleWild:
5539       case BlackKingSideCastleWild:
5540       case BlackQueenSideCastleWild:
5541       /* Code added by Tord: */
5542       case WhiteHSideCastleFR:
5543       case WhiteASideCastleFR:
5544       case BlackHSideCastleFR:
5545       case BlackASideCastleFR:
5546       /* End of code added by Tord */
5547       case IllegalMove:         /* bug or odd chess variant */
5548         if(currentMoveString[1] == '@') { // illegal drop
5549           *fromX = WhiteOnMove(moveNum) ?
5550             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5551             (int) CharToPiece(ToLower(currentMoveString[0]));
5552           goto drop;
5553         }
5554         *fromX = currentMoveString[0] - AAA;
5555         *fromY = currentMoveString[1] - ONE;
5556         *toX = currentMoveString[2] - AAA;
5557         *toY = currentMoveString[3] - ONE;
5558         *promoChar = currentMoveString[4];
5559         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5560             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5561     if (appData.debugMode) {
5562         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5563     }
5564             *fromX = *fromY = *toX = *toY = 0;
5565             return FALSE;
5566         }
5567         if (appData.testLegality) {
5568           return (*moveType != IllegalMove);
5569         } else {
5570           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5571                          // [HGM] lion: if this is a double move we are less critical
5572                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5573         }
5574
5575       case WhiteDrop:
5576       case BlackDrop:
5577         *fromX = *moveType == WhiteDrop ?
5578           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5579           (int) CharToPiece(ToLower(currentMoveString[0]));
5580       drop:
5581         *fromY = DROP_RANK;
5582         *toX = currentMoveString[2] - AAA;
5583         *toY = currentMoveString[3] - ONE;
5584         *promoChar = NULLCHAR;
5585         return TRUE;
5586
5587       case AmbiguousMove:
5588       case ImpossibleMove:
5589       case EndOfFile:
5590       case ElapsedTime:
5591       case Comment:
5592       case PGNTag:
5593       case NAG:
5594       case WhiteWins:
5595       case BlackWins:
5596       case GameIsDrawn:
5597       default:
5598     if (appData.debugMode) {
5599         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5600     }
5601         /* bug? */
5602         *fromX = *fromY = *toX = *toY = 0;
5603         *promoChar = NULLCHAR;
5604         return FALSE;
5605     }
5606 }
5607
5608 Boolean pushed = FALSE;
5609 char *lastParseAttempt;
5610
5611 void
5612 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5613 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5614   int fromX, fromY, toX, toY; char promoChar;
5615   ChessMove moveType;
5616   Boolean valid;
5617   int nr = 0;
5618
5619   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5620   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5621     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5622     pushed = TRUE;
5623   }
5624   endPV = forwardMostMove;
5625   do {
5626     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5627     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5628     lastParseAttempt = pv;
5629     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5630     if(!valid && nr == 0 &&
5631        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5632         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5633         // Hande case where played move is different from leading PV move
5634         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5635         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5636         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5637         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5638           endPV += 2; // if position different, keep this
5639           moveList[endPV-1][0] = fromX + AAA;
5640           moveList[endPV-1][1] = fromY + ONE;
5641           moveList[endPV-1][2] = toX + AAA;
5642           moveList[endPV-1][3] = toY + ONE;
5643           parseList[endPV-1][0] = NULLCHAR;
5644           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5645         }
5646       }
5647     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5648     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5649     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5650     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5651         valid++; // allow comments in PV
5652         continue;
5653     }
5654     nr++;
5655     if(endPV+1 > framePtr) break; // no space, truncate
5656     if(!valid) break;
5657     endPV++;
5658     CopyBoard(boards[endPV], boards[endPV-1]);
5659     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5660     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5661     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5662     CoordsToAlgebraic(boards[endPV - 1],
5663                              PosFlags(endPV - 1),
5664                              fromY, fromX, toY, toX, promoChar,
5665                              parseList[endPV - 1]);
5666   } while(valid);
5667   if(atEnd == 2) return; // used hidden, for PV conversion
5668   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5669   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5670   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5671                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5672   DrawPosition(TRUE, boards[currentMove]);
5673 }
5674
5675 int
5676 MultiPV (ChessProgramState *cps, int kind)
5677 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5678         int i;
5679         for(i=0; i<cps->nrOptions; i++) {
5680             char *s = cps->option[i].name;
5681             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5682             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5683                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5684         }
5685         return -1;
5686 }
5687
5688 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5689 static int multi, pv_margin;
5690 static ChessProgramState *activeCps;
5691
5692 Boolean
5693 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5694 {
5695         int startPV, lineStart, origIndex = index;
5696         char *p, buf2[MSG_SIZ];
5697         ChessProgramState *cps = (pane ? &second : &first);
5698
5699         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5700         lastX = x; lastY = y;
5701         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5702         lineStart = startPV = index;
5703         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5704         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5705         index = startPV;
5706         do{ while(buf[index] && buf[index] != '\n') index++;
5707         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5708         buf[index] = 0;
5709         if(lineStart == 0 && gameMode == AnalyzeMode) {
5710             int n = 0;
5711             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5712             if(n == 0) { // click not on "fewer" or "more"
5713                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5714                     pv_margin = cps->option[multi].value;
5715                     activeCps = cps; // non-null signals margin adjustment
5716                 }
5717             } else if((multi = MultiPV(cps, 1)) >= 0) {
5718                 n += cps->option[multi].value; if(n < 1) n = 1;
5719                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5720                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5721                 cps->option[multi].value = n;
5722                 *start = *end = 0;
5723                 return FALSE;
5724             }
5725         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5726                 ExcludeClick(origIndex - lineStart);
5727                 return FALSE;
5728         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5729                 Collapse(origIndex - lineStart);
5730                 return FALSE;
5731         }
5732         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5733         *start = startPV; *end = index-1;
5734         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5735         return TRUE;
5736 }
5737
5738 char *
5739 PvToSAN (char *pv)
5740 {
5741         static char buf[10*MSG_SIZ];
5742         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5743         *buf = NULLCHAR;
5744         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5745         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5746         for(i = forwardMostMove; i<endPV; i++){
5747             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5748             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5749             k += strlen(buf+k);
5750         }
5751         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5752         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5753         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5754         endPV = savedEnd;
5755         return buf;
5756 }
5757
5758 Boolean
5759 LoadPV (int x, int y)
5760 { // called on right mouse click to load PV
5761   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5762   lastX = x; lastY = y;
5763   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5764   extendGame = FALSE;
5765   return TRUE;
5766 }
5767
5768 void
5769 UnLoadPV ()
5770 {
5771   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5772   if(activeCps) {
5773     if(pv_margin != activeCps->option[multi].value) {
5774       char buf[MSG_SIZ];
5775       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5776       SendToProgram(buf, activeCps);
5777       activeCps->option[multi].value = pv_margin;
5778     }
5779     activeCps = NULL;
5780     return;
5781   }
5782   if(endPV < 0) return;
5783   if(appData.autoCopyPV) CopyFENToClipboard();
5784   endPV = -1;
5785   if(extendGame && currentMove > forwardMostMove) {
5786         Boolean saveAnimate = appData.animate;
5787         if(pushed) {
5788             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5789                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5790             } else storedGames--; // abandon shelved tail of original game
5791         }
5792         pushed = FALSE;
5793         forwardMostMove = currentMove;
5794         currentMove = oldFMM;
5795         appData.animate = FALSE;
5796         ToNrEvent(forwardMostMove);
5797         appData.animate = saveAnimate;
5798   }
5799   currentMove = forwardMostMove;
5800   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5801   ClearPremoveHighlights();
5802   DrawPosition(TRUE, boards[currentMove]);
5803 }
5804
5805 void
5806 MovePV (int x, int y, int h)
5807 { // step through PV based on mouse coordinates (called on mouse move)
5808   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5809
5810   if(activeCps) { // adjusting engine's multi-pv margin
5811     if(x > lastX) pv_margin++; else
5812     if(x < lastX) pv_margin -= (pv_margin > 0);
5813     if(x != lastX) {
5814       char buf[MSG_SIZ];
5815       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5816       DisplayMessage(buf, "");
5817     }
5818     lastX = x;
5819     return;
5820   }
5821   // we must somehow check if right button is still down (might be released off board!)
5822   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5823   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5824   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5825   if(!step) return;
5826   lastX = x; lastY = y;
5827
5828   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5829   if(endPV < 0) return;
5830   if(y < margin) step = 1; else
5831   if(y > h - margin) step = -1;
5832   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5833   currentMove += step;
5834   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5835   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5836                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5837   DrawPosition(FALSE, boards[currentMove]);
5838 }
5839
5840
5841 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5842 // All positions will have equal probability, but the current method will not provide a unique
5843 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5844 #define DARK 1
5845 #define LITE 2
5846 #define ANY 3
5847
5848 int squaresLeft[4];
5849 int piecesLeft[(int)BlackPawn];
5850 int seed, nrOfShuffles;
5851
5852 void
5853 GetPositionNumber ()
5854 {       // sets global variable seed
5855         int i;
5856
5857         seed = appData.defaultFrcPosition;
5858         if(seed < 0) { // randomize based on time for negative FRC position numbers
5859                 for(i=0; i<50; i++) seed += random();
5860                 seed = random() ^ random() >> 8 ^ random() << 8;
5861                 if(seed<0) seed = -seed;
5862         }
5863 }
5864
5865 int
5866 put (Board board, int pieceType, int rank, int n, int shade)
5867 // put the piece on the (n-1)-th empty squares of the given shade
5868 {
5869         int i;
5870
5871         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5872                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5873                         board[rank][i] = (ChessSquare) pieceType;
5874                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5875                         squaresLeft[ANY]--;
5876                         piecesLeft[pieceType]--;
5877                         return i;
5878                 }
5879         }
5880         return -1;
5881 }
5882
5883
5884 void
5885 AddOnePiece (Board board, int pieceType, int rank, int shade)
5886 // calculate where the next piece goes, (any empty square), and put it there
5887 {
5888         int i;
5889
5890         i = seed % squaresLeft[shade];
5891         nrOfShuffles *= squaresLeft[shade];
5892         seed /= squaresLeft[shade];
5893         put(board, pieceType, rank, i, shade);
5894 }
5895
5896 void
5897 AddTwoPieces (Board board, int pieceType, int rank)
5898 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5899 {
5900         int i, n=squaresLeft[ANY], j=n-1, k;
5901
5902         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5903         i = seed % k;  // pick one
5904         nrOfShuffles *= k;
5905         seed /= k;
5906         while(i >= j) i -= j--;
5907         j = n - 1 - j; i += j;
5908         put(board, pieceType, rank, j, ANY);
5909         put(board, pieceType, rank, i, ANY);
5910 }
5911
5912 void
5913 SetUpShuffle (Board board, int number)
5914 {
5915         int i, p, first=1;
5916
5917         GetPositionNumber(); nrOfShuffles = 1;
5918
5919         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5920         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5921         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5922
5923         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5924
5925         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5926             p = (int) board[0][i];
5927             if(p < (int) BlackPawn) piecesLeft[p] ++;
5928             board[0][i] = EmptySquare;
5929         }
5930
5931         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5932             // shuffles restricted to allow normal castling put KRR first
5933             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5934                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5935             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5936                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5937             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5938                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5939             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5940                 put(board, WhiteRook, 0, 0, ANY);
5941             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5942         }
5943
5944         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5945             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5946             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5947                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5948                 while(piecesLeft[p] >= 2) {
5949                     AddOnePiece(board, p, 0, LITE);
5950                     AddOnePiece(board, p, 0, DARK);
5951                 }
5952                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5953             }
5954
5955         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5956             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5957             // but we leave King and Rooks for last, to possibly obey FRC restriction
5958             if(p == (int)WhiteRook) continue;
5959             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5960             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5961         }
5962
5963         // now everything is placed, except perhaps King (Unicorn) and Rooks
5964
5965         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5966             // Last King gets castling rights
5967             while(piecesLeft[(int)WhiteUnicorn]) {
5968                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5969                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5970             }
5971
5972             while(piecesLeft[(int)WhiteKing]) {
5973                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5974                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5975             }
5976
5977
5978         } else {
5979             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5980             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5981         }
5982
5983         // Only Rooks can be left; simply place them all
5984         while(piecesLeft[(int)WhiteRook]) {
5985                 i = put(board, WhiteRook, 0, 0, ANY);
5986                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5987                         if(first) {
5988                                 first=0;
5989                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5990                         }
5991                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5992                 }
5993         }
5994         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5995             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5996         }
5997
5998         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5999 }
6000
6001 int
6002 ptclen (const char *s, char *escapes)
6003 {
6004     int n = 0;
6005     if(!*escapes) return strlen(s);
6006     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)), s++;
6007     return n;
6008 }
6009
6010 int
6011 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6012 /* [HGM] moved here from winboard.c because of its general usefulness */
6013 /*       Basically a safe strcpy that uses the last character as King */
6014 {
6015     int result = FALSE; int NrPieces;
6016     unsigned char partner[EmptySquare];
6017
6018     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6019                     && NrPieces >= 12 && !(NrPieces&1)) {
6020         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6021
6022         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6023         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6024             char *p, c=0;
6025             if(map[j] == '/') offs = WhitePBishop - i, j++;
6026             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6027             table[i+offs] = map[j++];
6028             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6029             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6030         }
6031         table[(int) WhiteKing]  = map[j++];
6032         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6033             char *p, c=0;
6034             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6035             i = WHITE_TO_BLACK ii;
6036             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6037             table[i+offs] = map[j++];
6038             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6039             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6040         }
6041         table[(int) BlackKing]  = map[j++];
6042
6043
6044         if(*escapes) { // set up promotion pairing
6045             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6046             // pieceToChar entirely filled, so we can look up specified partners
6047             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6048                 int c = table[i];
6049                 if(c == '^' || c == '-') { // has specified partner
6050                     int p;
6051                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6052                     if(c == '^') table[i] = '+';
6053                     if(p < EmptySquare) promoPartner[p] = i, promoPartner[i] = p; // marry them
6054                 } else if(c == '*') {
6055                     table[i] = partner[i];
6056                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6057                 }
6058             }
6059         }
6060
6061         result = TRUE;
6062     }
6063
6064     return result;
6065 }
6066
6067 int
6068 SetCharTable (unsigned char *table, const char * map)
6069 {
6070     return SetCharTableEsc(table, map, "");
6071 }
6072
6073 void
6074 Prelude (Board board)
6075 {       // [HGM] superchess: random selection of exo-pieces
6076         int i, j, k; ChessSquare p;
6077         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6078
6079         GetPositionNumber(); // use FRC position number
6080
6081         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6082             SetCharTable(pieceToChar, appData.pieceToCharTable);
6083             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6084                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6085         }
6086
6087         j = seed%4;                 seed /= 4;
6088         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6089         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6090         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6091         j = seed%3 + (seed%3 >= j); seed /= 3;
6092         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6093         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6094         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6095         j = seed%3;                 seed /= 3;
6096         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6097         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6098         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6099         j = seed%2 + (seed%2 >= j); seed /= 2;
6100         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6101         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6102         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6103         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6104         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6105         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6106         put(board, exoPieces[0],    0, 0, ANY);
6107         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6108 }
6109
6110 void
6111 InitPosition (int redraw)
6112 {
6113     ChessSquare (* pieces)[BOARD_FILES];
6114     int i, j, pawnRow=1, pieceRows=1, overrule,
6115     oldx = gameInfo.boardWidth,
6116     oldy = gameInfo.boardHeight,
6117     oldh = gameInfo.holdingsWidth;
6118     static int oldv;
6119
6120     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6121
6122     /* [AS] Initialize pv info list [HGM] and game status */
6123     {
6124         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6125             pvInfoList[i].depth = 0;
6126             boards[i][EP_STATUS] = EP_NONE;
6127             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6128         }
6129
6130         initialRulePlies = 0; /* 50-move counter start */
6131
6132         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6133         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6134     }
6135
6136
6137     /* [HGM] logic here is completely changed. In stead of full positions */
6138     /* the initialized data only consist of the two backranks. The switch */
6139     /* selects which one we will use, which is than copied to the Board   */
6140     /* initialPosition, which for the rest is initialized by Pawns and    */
6141     /* empty squares. This initial position is then copied to boards[0],  */
6142     /* possibly after shuffling, so that it remains available.            */
6143
6144     gameInfo.holdingsWidth = 0; /* default board sizes */
6145     gameInfo.boardWidth    = 8;
6146     gameInfo.boardHeight   = 8;
6147     gameInfo.holdingsSize  = 0;
6148     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6149     for(i=0; i<BOARD_FILES-6; i++)
6150       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6151     initialPosition[EP_STATUS] = EP_NONE;
6152     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6153     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6154     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6155          SetCharTable(pieceNickName, appData.pieceNickNames);
6156     else SetCharTable(pieceNickName, "............");
6157     pieces = FIDEArray;
6158
6159     switch (gameInfo.variant) {
6160     case VariantFischeRandom:
6161       shuffleOpenings = TRUE;
6162       appData.fischerCastling = TRUE;
6163     default:
6164       break;
6165     case VariantShatranj:
6166       pieces = ShatranjArray;
6167       nrCastlingRights = 0;
6168       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6169       break;
6170     case VariantMakruk:
6171       pieces = makrukArray;
6172       nrCastlingRights = 0;
6173       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6174       break;
6175     case VariantASEAN:
6176       pieces = aseanArray;
6177       nrCastlingRights = 0;
6178       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6179       break;
6180     case VariantTwoKings:
6181       pieces = twoKingsArray;
6182       break;
6183     case VariantGrand:
6184       pieces = GrandArray;
6185       nrCastlingRights = 0;
6186       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6187       gameInfo.boardWidth = 10;
6188       gameInfo.boardHeight = 10;
6189       gameInfo.holdingsSize = 7;
6190       break;
6191     case VariantCapaRandom:
6192       shuffleOpenings = TRUE;
6193       appData.fischerCastling = TRUE;
6194     case VariantCapablanca:
6195       pieces = CapablancaArray;
6196       gameInfo.boardWidth = 10;
6197       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6198       break;
6199     case VariantGothic:
6200       pieces = GothicArray;
6201       gameInfo.boardWidth = 10;
6202       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6203       break;
6204     case VariantSChess:
6205       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6206       gameInfo.holdingsSize = 7;
6207       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6208       break;
6209     case VariantJanus:
6210       pieces = JanusArray;
6211       gameInfo.boardWidth = 10;
6212       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6213       nrCastlingRights = 6;
6214         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6215         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6216         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6217         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6218         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6219         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6220       break;
6221     case VariantFalcon:
6222       pieces = FalconArray;
6223       gameInfo.boardWidth = 10;
6224       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6225       break;
6226     case VariantXiangqi:
6227       pieces = XiangqiArray;
6228       gameInfo.boardWidth  = 9;
6229       gameInfo.boardHeight = 10;
6230       nrCastlingRights = 0;
6231       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6232       break;
6233     case VariantShogi:
6234       pieces = ShogiArray;
6235       gameInfo.boardWidth  = 9;
6236       gameInfo.boardHeight = 9;
6237       gameInfo.holdingsSize = 7;
6238       nrCastlingRights = 0;
6239       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6240       break;
6241     case VariantChu:
6242       pieces = ChuArray; pieceRows = 3;
6243       gameInfo.boardWidth  = 12;
6244       gameInfo.boardHeight = 12;
6245       nrCastlingRights = 0;
6246       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6247                                    "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6248       break;
6249     case VariantCourier:
6250       pieces = CourierArray;
6251       gameInfo.boardWidth  = 12;
6252       nrCastlingRights = 0;
6253       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6254       break;
6255     case VariantKnightmate:
6256       pieces = KnightmateArray;
6257       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6258       break;
6259     case VariantSpartan:
6260       pieces = SpartanArray;
6261       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6262       break;
6263     case VariantLion:
6264       pieces = lionArray;
6265       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6266       break;
6267     case VariantChuChess:
6268       pieces = ChuChessArray;
6269       gameInfo.boardWidth = 10;
6270       gameInfo.boardHeight = 10;
6271       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6272       break;
6273     case VariantFairy:
6274       pieces = fairyArray;
6275       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6276       break;
6277     case VariantGreat:
6278       pieces = GreatArray;
6279       gameInfo.boardWidth = 10;
6280       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6281       gameInfo.holdingsSize = 8;
6282       break;
6283     case VariantSuper:
6284       pieces = FIDEArray;
6285       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6286       gameInfo.holdingsSize = 8;
6287       startedFromSetupPosition = TRUE;
6288       break;
6289     case VariantCrazyhouse:
6290     case VariantBughouse:
6291       pieces = FIDEArray;
6292       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6293       gameInfo.holdingsSize = 5;
6294       break;
6295     case VariantWildCastle:
6296       pieces = FIDEArray;
6297       /* !!?shuffle with kings guaranteed to be on d or e file */
6298       shuffleOpenings = 1;
6299       break;
6300     case VariantNoCastle:
6301       pieces = FIDEArray;
6302       nrCastlingRights = 0;
6303       /* !!?unconstrained back-rank shuffle */
6304       shuffleOpenings = 1;
6305       break;
6306     }
6307
6308     overrule = 0;
6309     if(appData.NrFiles >= 0) {
6310         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6311         gameInfo.boardWidth = appData.NrFiles;
6312     }
6313     if(appData.NrRanks >= 0) {
6314         gameInfo.boardHeight = appData.NrRanks;
6315     }
6316     if(appData.holdingsSize >= 0) {
6317         i = appData.holdingsSize;
6318         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6319         gameInfo.holdingsSize = i;
6320     }
6321     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6322     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6323         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6324
6325     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6326     if(pawnRow < 1) pawnRow = 1;
6327     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6328        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6329     if(gameInfo.variant == VariantChu) pawnRow = 3;
6330
6331     /* User pieceToChar list overrules defaults */
6332     if(appData.pieceToCharTable != NULL)
6333         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6334
6335     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6336
6337         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6338             s = (ChessSquare) 0; /* account holding counts in guard band */
6339         for( i=0; i<BOARD_HEIGHT; i++ )
6340             initialPosition[i][j] = s;
6341
6342         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6343         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6344         initialPosition[pawnRow][j] = WhitePawn;
6345         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6346         if(gameInfo.variant == VariantXiangqi) {
6347             if(j&1) {
6348                 initialPosition[pawnRow][j] =
6349                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6350                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6351                    initialPosition[2][j] = WhiteCannon;
6352                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6353                 }
6354             }
6355         }
6356         if(gameInfo.variant == VariantChu) {
6357              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6358                initialPosition[pawnRow+1][j] = WhiteCobra,
6359                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6360              for(i=1; i<pieceRows; i++) {
6361                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6362                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6363              }
6364         }
6365         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6366             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6367                initialPosition[0][j] = WhiteRook;
6368                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6369             }
6370         }
6371         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6372     }
6373     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6374     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6375
6376             j=BOARD_LEFT+1;
6377             initialPosition[1][j] = WhiteBishop;
6378             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6379             j=BOARD_RGHT-2;
6380             initialPosition[1][j] = WhiteRook;
6381             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6382     }
6383
6384     if( nrCastlingRights == -1) {
6385         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6386         /*       This sets default castling rights from none to normal corners   */
6387         /* Variants with other castling rights must set them themselves above    */
6388         nrCastlingRights = 6;
6389
6390         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6391         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6392         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6393         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6394         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6395         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6396      }
6397
6398      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6399      if(gameInfo.variant == VariantGreat) { // promotion commoners
6400         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6401         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6402         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6403         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6404      }
6405      if( gameInfo.variant == VariantSChess ) {
6406       initialPosition[1][0] = BlackMarshall;
6407       initialPosition[2][0] = BlackAngel;
6408       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6409       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6410       initialPosition[1][1] = initialPosition[2][1] =
6411       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6412      }
6413   if (appData.debugMode) {
6414     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6415   }
6416     if(shuffleOpenings) {
6417         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6418         startedFromSetupPosition = TRUE;
6419     }
6420     if(startedFromPositionFile) {
6421       /* [HGM] loadPos: use PositionFile for every new game */
6422       CopyBoard(initialPosition, filePosition);
6423       for(i=0; i<nrCastlingRights; i++)
6424           initialRights[i] = filePosition[CASTLING][i];
6425       startedFromSetupPosition = TRUE;
6426     }
6427
6428     CopyBoard(boards[0], initialPosition);
6429
6430     if(oldx != gameInfo.boardWidth ||
6431        oldy != gameInfo.boardHeight ||
6432        oldv != gameInfo.variant ||
6433        oldh != gameInfo.holdingsWidth
6434                                          )
6435             InitDrawingSizes(-2 ,0);
6436
6437     oldv = gameInfo.variant;
6438     if (redraw)
6439       DrawPosition(TRUE, boards[currentMove]);
6440 }
6441
6442 void
6443 SendBoard (ChessProgramState *cps, int moveNum)
6444 {
6445     char message[MSG_SIZ];
6446
6447     if (cps->useSetboard) {
6448       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6449       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6450       SendToProgram(message, cps);
6451       free(fen);
6452
6453     } else {
6454       ChessSquare *bp;
6455       int i, j, left=0, right=BOARD_WIDTH;
6456       /* Kludge to set black to move, avoiding the troublesome and now
6457        * deprecated "black" command.
6458        */
6459       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6460         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6461
6462       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6463
6464       SendToProgram("edit\n", cps);
6465       SendToProgram("#\n", cps);
6466       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6467         bp = &boards[moveNum][i][left];
6468         for (j = left; j < right; j++, bp++) {
6469           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6470           if ((int) *bp < (int) BlackPawn) {
6471             if(j == BOARD_RGHT+1)
6472                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6473             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6474             if(message[0] == '+' || message[0] == '~') {
6475               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6476                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6477                         AAA + j, ONE + i - '0');
6478             }
6479             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6480                 message[1] = BOARD_RGHT   - 1 - j + '1';
6481                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6482             }
6483             SendToProgram(message, cps);
6484           }
6485         }
6486       }
6487
6488       SendToProgram("c\n", cps);
6489       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6490         bp = &boards[moveNum][i][left];
6491         for (j = left; j < right; j++, bp++) {
6492           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6493           if (((int) *bp != (int) EmptySquare)
6494               && ((int) *bp >= (int) BlackPawn)) {
6495             if(j == BOARD_LEFT-2)
6496                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6497             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6498                     AAA + j, ONE + i - '0');
6499             if(message[0] == '+' || message[0] == '~') {
6500               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6501                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6502                         AAA + j, ONE + i - '0');
6503             }
6504             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6505                 message[1] = BOARD_RGHT   - 1 - j + '1';
6506                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6507             }
6508             SendToProgram(message, cps);
6509           }
6510         }
6511       }
6512
6513       SendToProgram(".\n", cps);
6514     }
6515     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6516 }
6517
6518 char exclusionHeader[MSG_SIZ];
6519 int exCnt, excludePtr;
6520 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6521 static Exclusion excluTab[200];
6522 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6523
6524 static void
6525 WriteMap (int s)
6526 {
6527     int j;
6528     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6529     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6530 }
6531
6532 static void
6533 ClearMap ()
6534 {
6535     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6536     excludePtr = 24; exCnt = 0;
6537     WriteMap(0);
6538 }
6539
6540 static void
6541 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6542 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6543     char buf[2*MOVE_LEN], *p;
6544     Exclusion *e = excluTab;
6545     int i;
6546     for(i=0; i<exCnt; i++)
6547         if(e[i].ff == fromX && e[i].fr == fromY &&
6548            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6549     if(i == exCnt) { // was not in exclude list; add it
6550         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6551         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6552             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6553             return; // abort
6554         }
6555         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6556         excludePtr++; e[i].mark = excludePtr++;
6557         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6558         exCnt++;
6559     }
6560     exclusionHeader[e[i].mark] = state;
6561 }
6562
6563 static int
6564 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6565 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6566     char buf[MSG_SIZ];
6567     int j, k;
6568     ChessMove moveType;
6569     if((signed char)promoChar == -1) { // kludge to indicate best move
6570         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6571             return 1; // if unparsable, abort
6572     }
6573     // update exclusion map (resolving toggle by consulting existing state)
6574     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6575     j = k%8; k >>= 3;
6576     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6577     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6578          excludeMap[k] |=   1<<j;
6579     else excludeMap[k] &= ~(1<<j);
6580     // update header
6581     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6582     // inform engine
6583     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6584     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6585     SendToBoth(buf);
6586     return (state == '+');
6587 }
6588
6589 static void
6590 ExcludeClick (int index)
6591 {
6592     int i, j;
6593     Exclusion *e = excluTab;
6594     if(index < 25) { // none, best or tail clicked
6595         if(index < 13) { // none: include all
6596             WriteMap(0); // clear map
6597             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6598             SendToBoth("include all\n"); // and inform engine
6599         } else if(index > 18) { // tail
6600             if(exclusionHeader[19] == '-') { // tail was excluded
6601                 SendToBoth("include all\n");
6602                 WriteMap(0); // clear map completely
6603                 // now re-exclude selected moves
6604                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6605                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6606             } else { // tail was included or in mixed state
6607                 SendToBoth("exclude all\n");
6608                 WriteMap(0xFF); // fill map completely
6609                 // now re-include selected moves
6610                 j = 0; // count them
6611                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6612                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6613                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6614             }
6615         } else { // best
6616             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6617         }
6618     } else {
6619         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6620             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6621             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6622             break;
6623         }
6624     }
6625 }
6626
6627 ChessSquare
6628 DefaultPromoChoice (int white)
6629 {
6630     ChessSquare result;
6631     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6632        gameInfo.variant == VariantMakruk)
6633         result = WhiteFerz; // no choice
6634     else if(gameInfo.variant == VariantASEAN)
6635         result = WhiteRook; // no choice
6636     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6637         result= WhiteKing; // in Suicide Q is the last thing we want
6638     else if(gameInfo.variant == VariantSpartan)
6639         result = white ? WhiteQueen : WhiteAngel;
6640     else result = WhiteQueen;
6641     if(!white) result = WHITE_TO_BLACK result;
6642     return result;
6643 }
6644
6645 static int autoQueen; // [HGM] oneclick
6646
6647 int
6648 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6649 {
6650     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6651     /* [HGM] add Shogi promotions */
6652     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6653     ChessSquare piece, partner;
6654     ChessMove moveType;
6655     Boolean premove;
6656
6657     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6658     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6659
6660     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6661       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6662         return FALSE;
6663
6664     piece = boards[currentMove][fromY][fromX];
6665     if(gameInfo.variant == VariantChu) {
6666         promotionZoneSize = BOARD_HEIGHT/3;
6667         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6668     } else if(gameInfo.variant == VariantShogi) {
6669         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6670         highestPromotingPiece = (int)WhiteAlfil;
6671     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6672         promotionZoneSize = 3;
6673     }
6674
6675     // Treat Lance as Pawn when it is not representing Amazon or Lance
6676     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6677         if(piece == WhiteLance) piece = WhitePawn; else
6678         if(piece == BlackLance) piece = BlackPawn;
6679     }
6680
6681     // next weed out all moves that do not touch the promotion zone at all
6682     if((int)piece >= BlackPawn) {
6683         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6684              return FALSE;
6685         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6686         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6687     } else {
6688         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6689            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6690         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6691              return FALSE;
6692     }
6693
6694     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6695
6696     // weed out mandatory Shogi promotions
6697     if(gameInfo.variant == VariantShogi) {
6698         if(piece >= BlackPawn) {
6699             if(toY == 0 && piece == BlackPawn ||
6700                toY == 0 && piece == BlackQueen ||
6701                toY <= 1 && piece == BlackKnight) {
6702                 *promoChoice = '+';
6703                 return FALSE;
6704             }
6705         } else {
6706             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6707                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6708                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6709                 *promoChoice = '+';
6710                 return FALSE;
6711             }
6712         }
6713     }
6714
6715     // weed out obviously illegal Pawn moves
6716     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6717         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6718         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6719         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6720         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6721         // note we are not allowed to test for valid (non-)capture, due to premove
6722     }
6723
6724     // we either have a choice what to promote to, or (in Shogi) whether to promote
6725     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6726        gameInfo.variant == VariantMakruk) {
6727         ChessSquare p=BlackFerz;  // no choice
6728         while(p < EmptySquare) {  //but make sure we use piece that exists
6729             *promoChoice = PieceToChar(p++);
6730             if(*promoChoice != '.') break;
6731         }
6732         return FALSE;
6733     }
6734     // no sense asking what we must promote to if it is going to explode...
6735     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6736         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6737         return FALSE;
6738     }
6739     // give caller the default choice even if we will not make it
6740     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6741     partner = piece; // pieces can promote if the pieceToCharTable says so
6742     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6743     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6744     if(        sweepSelect && gameInfo.variant != VariantGreat
6745                            && gameInfo.variant != VariantGrand
6746                            && gameInfo.variant != VariantSuper) return FALSE;
6747     if(autoQueen) return FALSE; // predetermined
6748
6749     // suppress promotion popup on illegal moves that are not premoves
6750     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6751               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6752     if(appData.testLegality && !premove) {
6753         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6754                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6755         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6756         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6757             return FALSE;
6758     }
6759
6760     return TRUE;
6761 }
6762
6763 int
6764 InPalace (int row, int column)
6765 {   /* [HGM] for Xiangqi */
6766     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6767          column < (BOARD_WIDTH + 4)/2 &&
6768          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6769     return FALSE;
6770 }
6771
6772 int
6773 PieceForSquare (int x, int y)
6774 {
6775   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6776      return -1;
6777   else
6778      return boards[currentMove][y][x];
6779 }
6780
6781 int
6782 OKToStartUserMove (int x, int y)
6783 {
6784     ChessSquare from_piece;
6785     int white_piece;
6786
6787     if (matchMode) return FALSE;
6788     if (gameMode == EditPosition) return TRUE;
6789
6790     if (x >= 0 && y >= 0)
6791       from_piece = boards[currentMove][y][x];
6792     else
6793       from_piece = EmptySquare;
6794
6795     if (from_piece == EmptySquare) return FALSE;
6796
6797     white_piece = (int)from_piece >= (int)WhitePawn &&
6798       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6799
6800     switch (gameMode) {
6801       case AnalyzeFile:
6802       case TwoMachinesPlay:
6803       case EndOfGame:
6804         return FALSE;
6805
6806       case IcsObserving:
6807       case IcsIdle:
6808         return FALSE;
6809
6810       case MachinePlaysWhite:
6811       case IcsPlayingBlack:
6812         if (appData.zippyPlay) return FALSE;
6813         if (white_piece) {
6814             DisplayMoveError(_("You are playing Black"));
6815             return FALSE;
6816         }
6817         break;
6818
6819       case MachinePlaysBlack:
6820       case IcsPlayingWhite:
6821         if (appData.zippyPlay) return FALSE;
6822         if (!white_piece) {
6823             DisplayMoveError(_("You are playing White"));
6824             return FALSE;
6825         }
6826         break;
6827
6828       case PlayFromGameFile:
6829             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6830       case EditGame:
6831         if (!white_piece && WhiteOnMove(currentMove)) {
6832             DisplayMoveError(_("It is White's turn"));
6833             return FALSE;
6834         }
6835         if (white_piece && !WhiteOnMove(currentMove)) {
6836             DisplayMoveError(_("It is Black's turn"));
6837             return FALSE;
6838         }
6839         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6840             /* Editing correspondence game history */
6841             /* Could disallow this or prompt for confirmation */
6842             cmailOldMove = -1;
6843         }
6844         break;
6845
6846       case BeginningOfGame:
6847         if (appData.icsActive) return FALSE;
6848         if (!appData.noChessProgram) {
6849             if (!white_piece) {
6850                 DisplayMoveError(_("You are playing White"));
6851                 return FALSE;
6852             }
6853         }
6854         break;
6855
6856       case Training:
6857         if (!white_piece && WhiteOnMove(currentMove)) {
6858             DisplayMoveError(_("It is White's turn"));
6859             return FALSE;
6860         }
6861         if (white_piece && !WhiteOnMove(currentMove)) {
6862             DisplayMoveError(_("It is Black's turn"));
6863             return FALSE;
6864         }
6865         break;
6866
6867       default:
6868       case IcsExamining:
6869         break;
6870     }
6871     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6872         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6873         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6874         && gameMode != AnalyzeFile && gameMode != Training) {
6875         DisplayMoveError(_("Displayed position is not current"));
6876         return FALSE;
6877     }
6878     return TRUE;
6879 }
6880
6881 Boolean
6882 OnlyMove (int *x, int *y, Boolean captures)
6883 {
6884     DisambiguateClosure cl;
6885     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6886     switch(gameMode) {
6887       case MachinePlaysBlack:
6888       case IcsPlayingWhite:
6889       case BeginningOfGame:
6890         if(!WhiteOnMove(currentMove)) return FALSE;
6891         break;
6892       case MachinePlaysWhite:
6893       case IcsPlayingBlack:
6894         if(WhiteOnMove(currentMove)) return FALSE;
6895         break;
6896       case EditGame:
6897         break;
6898       default:
6899         return FALSE;
6900     }
6901     cl.pieceIn = EmptySquare;
6902     cl.rfIn = *y;
6903     cl.ffIn = *x;
6904     cl.rtIn = -1;
6905     cl.ftIn = -1;
6906     cl.promoCharIn = NULLCHAR;
6907     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6908     if( cl.kind == NormalMove ||
6909         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6910         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6911         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6912       fromX = cl.ff;
6913       fromY = cl.rf;
6914       *x = cl.ft;
6915       *y = cl.rt;
6916       return TRUE;
6917     }
6918     if(cl.kind != ImpossibleMove) return FALSE;
6919     cl.pieceIn = EmptySquare;
6920     cl.rfIn = -1;
6921     cl.ffIn = -1;
6922     cl.rtIn = *y;
6923     cl.ftIn = *x;
6924     cl.promoCharIn = NULLCHAR;
6925     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6926     if( cl.kind == NormalMove ||
6927         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6928         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6929         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6930       fromX = cl.ff;
6931       fromY = cl.rf;
6932       *x = cl.ft;
6933       *y = cl.rt;
6934       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6935       return TRUE;
6936     }
6937     return FALSE;
6938 }
6939
6940 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6941 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6942 int lastLoadGameUseList = FALSE;
6943 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6944 ChessMove lastLoadGameStart = EndOfFile;
6945 int doubleClick;
6946 Boolean addToBookFlag;
6947
6948 void
6949 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6950 {
6951     ChessMove moveType;
6952     ChessSquare pup;
6953     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6954
6955     /* Check if the user is playing in turn.  This is complicated because we
6956        let the user "pick up" a piece before it is his turn.  So the piece he
6957        tried to pick up may have been captured by the time he puts it down!
6958        Therefore we use the color the user is supposed to be playing in this
6959        test, not the color of the piece that is currently on the starting
6960        square---except in EditGame mode, where the user is playing both
6961        sides; fortunately there the capture race can't happen.  (It can
6962        now happen in IcsExamining mode, but that's just too bad.  The user
6963        will get a somewhat confusing message in that case.)
6964        */
6965
6966     switch (gameMode) {
6967       case AnalyzeFile:
6968       case TwoMachinesPlay:
6969       case EndOfGame:
6970       case IcsObserving:
6971       case IcsIdle:
6972         /* We switched into a game mode where moves are not accepted,
6973            perhaps while the mouse button was down. */
6974         return;
6975
6976       case MachinePlaysWhite:
6977         /* User is moving for Black */
6978         if (WhiteOnMove(currentMove)) {
6979             DisplayMoveError(_("It is White's turn"));
6980             return;
6981         }
6982         break;
6983
6984       case MachinePlaysBlack:
6985         /* User is moving for White */
6986         if (!WhiteOnMove(currentMove)) {
6987             DisplayMoveError(_("It is Black's turn"));
6988             return;
6989         }
6990         break;
6991
6992       case PlayFromGameFile:
6993             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6994       case EditGame:
6995       case IcsExamining:
6996       case BeginningOfGame:
6997       case AnalyzeMode:
6998       case Training:
6999         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7000         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7001             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7002             /* User is moving for Black */
7003             if (WhiteOnMove(currentMove)) {
7004                 DisplayMoveError(_("It is White's turn"));
7005                 return;
7006             }
7007         } else {
7008             /* User is moving for White */
7009             if (!WhiteOnMove(currentMove)) {
7010                 DisplayMoveError(_("It is Black's turn"));
7011                 return;
7012             }
7013         }
7014         break;
7015
7016       case IcsPlayingBlack:
7017         /* User is moving for Black */
7018         if (WhiteOnMove(currentMove)) {
7019             if (!appData.premove) {
7020                 DisplayMoveError(_("It is White's turn"));
7021             } else if (toX >= 0 && toY >= 0) {
7022                 premoveToX = toX;
7023                 premoveToY = toY;
7024                 premoveFromX = fromX;
7025                 premoveFromY = fromY;
7026                 premovePromoChar = promoChar;
7027                 gotPremove = 1;
7028                 if (appData.debugMode)
7029                     fprintf(debugFP, "Got premove: fromX %d,"
7030                             "fromY %d, toX %d, toY %d\n",
7031                             fromX, fromY, toX, toY);
7032             }
7033             return;
7034         }
7035         break;
7036
7037       case IcsPlayingWhite:
7038         /* User is moving for White */
7039         if (!WhiteOnMove(currentMove)) {
7040             if (!appData.premove) {
7041                 DisplayMoveError(_("It is Black's turn"));
7042             } else if (toX >= 0 && toY >= 0) {
7043                 premoveToX = toX;
7044                 premoveToY = toY;
7045                 premoveFromX = fromX;
7046                 premoveFromY = fromY;
7047                 premovePromoChar = promoChar;
7048                 gotPremove = 1;
7049                 if (appData.debugMode)
7050                     fprintf(debugFP, "Got premove: fromX %d,"
7051                             "fromY %d, toX %d, toY %d\n",
7052                             fromX, fromY, toX, toY);
7053             }
7054             return;
7055         }
7056         break;
7057
7058       default:
7059         break;
7060
7061       case EditPosition:
7062         /* EditPosition, empty square, or different color piece;
7063            click-click move is possible */
7064         if (toX == -2 || toY == -2) {
7065             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7066             DrawPosition(FALSE, boards[currentMove]);
7067             return;
7068         } else if (toX >= 0 && toY >= 0) {
7069             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7070                 ChessSquare p = boards[0][rf][ff];
7071                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7072                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); 
7073             }
7074             boards[0][toY][toX] = boards[0][fromY][fromX];
7075             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7076                 if(boards[0][fromY][0] != EmptySquare) {
7077                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7078                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7079                 }
7080             } else
7081             if(fromX == BOARD_RGHT+1) {
7082                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7083                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7084                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7085                 }
7086             } else
7087             boards[0][fromY][fromX] = gatingPiece;
7088             ClearHighlights();
7089             DrawPosition(FALSE, boards[currentMove]);
7090             return;
7091         }
7092         return;
7093     }
7094
7095     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7096     pup = boards[currentMove][toY][toX];
7097
7098     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7099     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7100          if( pup != EmptySquare ) return;
7101          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7102            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7103                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7104            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7105            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7106            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7107            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7108          fromY = DROP_RANK;
7109     }
7110
7111     /* [HGM] always test for legality, to get promotion info */
7112     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7113                                          fromY, fromX, toY, toX, promoChar);
7114
7115     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7116
7117     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7118
7119     /* [HGM] but possibly ignore an IllegalMove result */
7120     if (appData.testLegality) {
7121         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7122             DisplayMoveError(_("Illegal move"));
7123             return;
7124         }
7125     }
7126
7127     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7128         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7129              ClearPremoveHighlights(); // was included
7130         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7131         return;
7132     }
7133
7134     if(addToBookFlag) { // adding moves to book
7135         char buf[MSG_SIZ], move[MSG_SIZ];
7136         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7137         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d", fromX + AAA, fromY + ONE - '0', killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0');
7138         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7139         AddBookMove(buf);
7140         addToBookFlag = FALSE;
7141         ClearHighlights();
7142         return;
7143     }
7144
7145     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7146 }
7147
7148 /* Common tail of UserMoveEvent and DropMenuEvent */
7149 int
7150 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7151 {
7152     char *bookHit = 0;
7153
7154     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7155         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7156         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7157         if(WhiteOnMove(currentMove)) {
7158             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7159         } else {
7160             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7161         }
7162     }
7163
7164     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7165        move type in caller when we know the move is a legal promotion */
7166     if(moveType == NormalMove && promoChar)
7167         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7168
7169     /* [HGM] <popupFix> The following if has been moved here from
7170        UserMoveEvent(). Because it seemed to belong here (why not allow
7171        piece drops in training games?), and because it can only be
7172        performed after it is known to what we promote. */
7173     if (gameMode == Training) {
7174       /* compare the move played on the board to the next move in the
7175        * game. If they match, display the move and the opponent's response.
7176        * If they don't match, display an error message.
7177        */
7178       int saveAnimate;
7179       Board testBoard;
7180       CopyBoard(testBoard, boards[currentMove]);
7181       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7182
7183       if (CompareBoards(testBoard, boards[currentMove+1])) {
7184         ForwardInner(currentMove+1);
7185
7186         /* Autoplay the opponent's response.
7187          * if appData.animate was TRUE when Training mode was entered,
7188          * the response will be animated.
7189          */
7190         saveAnimate = appData.animate;
7191         appData.animate = animateTraining;
7192         ForwardInner(currentMove+1);
7193         appData.animate = saveAnimate;
7194
7195         /* check for the end of the game */
7196         if (currentMove >= forwardMostMove) {
7197           gameMode = PlayFromGameFile;
7198           ModeHighlight();
7199           SetTrainingModeOff();
7200           DisplayInformation(_("End of game"));
7201         }
7202       } else {
7203         DisplayError(_("Incorrect move"), 0);
7204       }
7205       return 1;
7206     }
7207
7208   /* Ok, now we know that the move is good, so we can kill
7209      the previous line in Analysis Mode */
7210   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7211                                 && currentMove < forwardMostMove) {
7212     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7213     else forwardMostMove = currentMove;
7214   }
7215
7216   ClearMap();
7217
7218   /* If we need the chess program but it's dead, restart it */
7219   ResurrectChessProgram();
7220
7221   /* A user move restarts a paused game*/
7222   if (pausing)
7223     PauseEvent();
7224
7225   thinkOutput[0] = NULLCHAR;
7226
7227   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7228
7229   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7230     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7231     return 1;
7232   }
7233
7234   if (gameMode == BeginningOfGame) {
7235     if (appData.noChessProgram) {
7236       gameMode = EditGame;
7237       SetGameInfo();
7238     } else {
7239       char buf[MSG_SIZ];
7240       gameMode = MachinePlaysBlack;
7241       StartClocks();
7242       SetGameInfo();
7243       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7244       DisplayTitle(buf);
7245       if (first.sendName) {
7246         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7247         SendToProgram(buf, &first);
7248       }
7249       StartClocks();
7250     }
7251     ModeHighlight();
7252   }
7253
7254   /* Relay move to ICS or chess engine */
7255   if (appData.icsActive) {
7256     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7257         gameMode == IcsExamining) {
7258       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7259         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7260         SendToICS("draw ");
7261         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7262       }
7263       // also send plain move, in case ICS does not understand atomic claims
7264       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7265       ics_user_moved = 1;
7266     }
7267   } else {
7268     if (first.sendTime && (gameMode == BeginningOfGame ||
7269                            gameMode == MachinePlaysWhite ||
7270                            gameMode == MachinePlaysBlack)) {
7271       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7272     }
7273     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7274          // [HGM] book: if program might be playing, let it use book
7275         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7276         first.maybeThinking = TRUE;
7277     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7278         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7279         SendBoard(&first, currentMove+1);
7280         if(second.analyzing) {
7281             if(!second.useSetboard) SendToProgram("undo\n", &second);
7282             SendBoard(&second, currentMove+1);
7283         }
7284     } else {
7285         SendMoveToProgram(forwardMostMove-1, &first);
7286         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7287     }
7288     if (currentMove == cmailOldMove + 1) {
7289       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7290     }
7291   }
7292
7293   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7294
7295   switch (gameMode) {
7296   case EditGame:
7297     if(appData.testLegality)
7298     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7299     case MT_NONE:
7300     case MT_CHECK:
7301       break;
7302     case MT_CHECKMATE:
7303     case MT_STAINMATE:
7304       if (WhiteOnMove(currentMove)) {
7305         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7306       } else {
7307         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7308       }
7309       break;
7310     case MT_STALEMATE:
7311       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7312       break;
7313     }
7314     break;
7315
7316   case MachinePlaysBlack:
7317   case MachinePlaysWhite:
7318     /* disable certain menu options while machine is thinking */
7319     SetMachineThinkingEnables();
7320     break;
7321
7322   default:
7323     break;
7324   }
7325
7326   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7327   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7328
7329   if(bookHit) { // [HGM] book: simulate book reply
7330         static char bookMove[MSG_SIZ]; // a bit generous?
7331
7332         programStats.nodes = programStats.depth = programStats.time =
7333         programStats.score = programStats.got_only_move = 0;
7334         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7335
7336         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7337         strcat(bookMove, bookHit);
7338         HandleMachineMove(bookMove, &first);
7339   }
7340   return 1;
7341 }
7342
7343 void
7344 MarkByFEN(char *fen)
7345 {
7346         int r, f;
7347         if(!appData.markers || !appData.highlightDragging) return;
7348         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7349         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7350         while(*fen) {
7351             int s = 0;
7352             marker[r][f] = 0;
7353             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7354             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7355             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7356             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7357             if(*fen == 'T') marker[r][f++] = 0; else
7358             if(*fen == 'Y') marker[r][f++] = 1; else
7359             if(*fen == 'G') marker[r][f++] = 3; else
7360             if(*fen == 'B') marker[r][f++] = 4; else
7361             if(*fen == 'C') marker[r][f++] = 5; else
7362             if(*fen == 'M') marker[r][f++] = 6; else
7363             if(*fen == 'W') marker[r][f++] = 7; else
7364             if(*fen == 'D') marker[r][f++] = 8; else
7365             if(*fen == 'R') marker[r][f++] = 2; else {
7366                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7367               f += s; fen -= s>0;
7368             }
7369             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7370             if(r < 0) break;
7371             fen++;
7372         }
7373         DrawPosition(TRUE, NULL);
7374 }
7375
7376 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7377
7378 void
7379 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7380 {
7381     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7382     Markers *m = (Markers *) closure;
7383     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7384         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7385                          || kind == WhiteCapturesEnPassant
7386                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 3;
7387     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7388 }
7389
7390 static int hoverSavedValid;
7391
7392 void
7393 MarkTargetSquares (int clear)
7394 {
7395   int x, y, sum=0;
7396   if(clear) { // no reason to ever suppress clearing
7397     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7398     hoverSavedValid = 0;
7399     if(!sum) return; // nothing was cleared,no redraw needed
7400   } else {
7401     int capt = 0;
7402     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7403        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7404     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7405     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7406       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7407       if(capt)
7408       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7409     }
7410   }
7411   DrawPosition(FALSE, NULL);
7412 }
7413
7414 int
7415 Explode (Board board, int fromX, int fromY, int toX, int toY)
7416 {
7417     if(gameInfo.variant == VariantAtomic &&
7418        (board[toY][toX] != EmptySquare ||                     // capture?
7419         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7420                          board[fromY][fromX] == BlackPawn   )
7421       )) {
7422         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7423         return TRUE;
7424     }
7425     return FALSE;
7426 }
7427
7428 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7429
7430 int
7431 CanPromote (ChessSquare piece, int y)
7432 {
7433         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7434         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7435         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7436         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7437            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7438            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7439          gameInfo.variant == VariantMakruk) return FALSE;
7440         return (piece == BlackPawn && y <= zone ||
7441                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7442                 piece == BlackLance && y <= zone ||
7443                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7444 }
7445
7446 void
7447 HoverEvent (int xPix, int yPix, int x, int y)
7448 {
7449         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7450         int r, f;
7451         if(!first.highlight) return;
7452         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7453         if(x == oldX && y == oldY) return; // only do something if we enter new square
7454         oldFromX = fromX; oldFromY = fromY;
7455         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7456           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7457             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7458           hoverSavedValid = 1;
7459         } else if(oldX != x || oldY != y) {
7460           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7461           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7462           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7463             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7464           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7465             char buf[MSG_SIZ];
7466             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7467             SendToProgram(buf, &first);
7468           }
7469           oldX = x; oldY = y;
7470 //        SetHighlights(fromX, fromY, x, y);
7471         }
7472 }
7473
7474 void ReportClick(char *action, int x, int y)
7475 {
7476         char buf[MSG_SIZ]; // Inform engine of what user does
7477         int r, f;
7478         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7479           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7480             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7481         if(!first.highlight || gameMode == EditPosition) return;
7482         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7483         SendToProgram(buf, &first);
7484 }
7485
7486 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7487
7488 void
7489 LeftClick (ClickType clickType, int xPix, int yPix)
7490 {
7491     int x, y;
7492     Boolean saveAnimate;
7493     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7494     char promoChoice = NULLCHAR;
7495     ChessSquare piece;
7496     static TimeMark lastClickTime, prevClickTime;
7497
7498     x = EventToSquare(xPix, BOARD_WIDTH);
7499     y = EventToSquare(yPix, BOARD_HEIGHT);
7500     if (!flipView && y >= 0) {
7501         y = BOARD_HEIGHT - 1 - y;
7502     }
7503     if (flipView && x >= 0) {
7504         x = BOARD_WIDTH - 1 - x;
7505     }
7506
7507     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7508         static int dummy;
7509         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7510         right = TRUE;
7511         return;
7512     }
7513
7514     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7515
7516     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7517
7518     if (clickType == Press) ErrorPopDown();
7519     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7520
7521     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7522         defaultPromoChoice = promoSweep;
7523         promoSweep = EmptySquare;   // terminate sweep
7524         promoDefaultAltered = TRUE;
7525         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7526     }
7527
7528     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7529         if(clickType == Release) return; // ignore upclick of click-click destination
7530         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7531         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7532         if(gameInfo.holdingsWidth &&
7533                 (WhiteOnMove(currentMove)
7534                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7535                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7536             // click in right holdings, for determining promotion piece
7537             ChessSquare p = boards[currentMove][y][x];
7538             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7539             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7540             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7541                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7542                 fromX = fromY = -1;
7543                 return;
7544             }
7545         }
7546         DrawPosition(FALSE, boards[currentMove]);
7547         return;
7548     }
7549
7550     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7551     if(clickType == Press
7552             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7553               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7554               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7555         return;
7556
7557     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7558         // could be static click on premove from-square: abort premove
7559         gotPremove = 0;
7560         ClearPremoveHighlights();
7561     }
7562
7563     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7564         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7565
7566     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7567         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7568                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7569         defaultPromoChoice = DefaultPromoChoice(side);
7570     }
7571
7572     autoQueen = appData.alwaysPromoteToQueen;
7573
7574     if (fromX == -1) {
7575       int originalY = y;
7576       gatingPiece = EmptySquare;
7577       if (clickType != Press) {
7578         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7579             DragPieceEnd(xPix, yPix); dragging = 0;
7580             DrawPosition(FALSE, NULL);
7581         }
7582         return;
7583       }
7584       doubleClick = FALSE;
7585       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7586         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7587       }
7588       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7589       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7590          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7591          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7592             /* First square */
7593             if (OKToStartUserMove(fromX, fromY)) {
7594                 second = 0;
7595                 ReportClick("lift", x, y);
7596                 MarkTargetSquares(0);
7597                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7598                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7599                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7600                     promoSweep = defaultPromoChoice;
7601                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7602                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7603                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7604                 }
7605                 if (appData.highlightDragging) {
7606                     SetHighlights(fromX, fromY, -1, -1);
7607                 } else {
7608                     ClearHighlights();
7609                 }
7610             } else fromX = fromY = -1;
7611             return;
7612         }
7613     }
7614
7615     /* fromX != -1 */
7616     if (clickType == Press && gameMode != EditPosition) {
7617         ChessSquare fromP;
7618         ChessSquare toP;
7619         int frc;
7620
7621         // ignore off-board to clicks
7622         if(y < 0 || x < 0) return;
7623
7624         /* Check if clicking again on the same color piece */
7625         fromP = boards[currentMove][fromY][fromX];
7626         toP = boards[currentMove][y][x];
7627         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7628         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7629             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7630            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7631              WhitePawn <= toP && toP <= WhiteKing &&
7632              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7633              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7634             (BlackPawn <= fromP && fromP <= BlackKing &&
7635              BlackPawn <= toP && toP <= BlackKing &&
7636              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7637              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7638             /* Clicked again on same color piece -- changed his mind */
7639             second = (x == fromX && y == fromY);
7640             killX = killY = -1;
7641             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7642                 second = FALSE; // first double-click rather than scond click
7643                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7644             }
7645             promoDefaultAltered = FALSE;
7646             MarkTargetSquares(1);
7647            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7648             if (appData.highlightDragging) {
7649                 SetHighlights(x, y, -1, -1);
7650             } else {
7651                 ClearHighlights();
7652             }
7653             if (OKToStartUserMove(x, y)) {
7654                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7655                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7656                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7657                  gatingPiece = boards[currentMove][fromY][fromX];
7658                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7659                 fromX = x;
7660                 fromY = y; dragging = 1;
7661                 if(!second) ReportClick("lift", x, y);
7662                 MarkTargetSquares(0);
7663                 DragPieceBegin(xPix, yPix, FALSE);
7664                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7665                     promoSweep = defaultPromoChoice;
7666                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7667                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7668                 }
7669             }
7670            }
7671            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7672            second = FALSE;
7673         }
7674         // ignore clicks on holdings
7675         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7676     }
7677
7678     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7679         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7680         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7681         return;
7682     }
7683
7684     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7685         DragPieceEnd(xPix, yPix); dragging = 0;
7686         if(clearFlag) {
7687             // a deferred attempt to click-click move an empty square on top of a piece
7688             boards[currentMove][y][x] = EmptySquare;
7689             ClearHighlights();
7690             DrawPosition(FALSE, boards[currentMove]);
7691             fromX = fromY = -1; clearFlag = 0;
7692             return;
7693         }
7694         if (appData.animateDragging) {
7695             /* Undo animation damage if any */
7696             DrawPosition(FALSE, NULL);
7697         }
7698         if (second) {
7699             /* Second up/down in same square; just abort move */
7700             second = 0;
7701             fromX = fromY = -1;
7702             gatingPiece = EmptySquare;
7703             MarkTargetSquares(1);
7704             ClearHighlights();
7705             gotPremove = 0;
7706             ClearPremoveHighlights();
7707         } else {
7708             /* First upclick in same square; start click-click mode */
7709             SetHighlights(x, y, -1, -1);
7710         }
7711         return;
7712     }
7713
7714     clearFlag = 0;
7715
7716     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7717        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7718         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7719         DisplayMessage(_("only marked squares are legal"),"");
7720         DrawPosition(TRUE, NULL);
7721         return; // ignore to-click
7722     }
7723
7724     /* we now have a different from- and (possibly off-board) to-square */
7725     /* Completed move */
7726     if(!sweepSelecting) {
7727         toX = x;
7728         toY = y;
7729     }
7730
7731     piece = boards[currentMove][fromY][fromX];
7732
7733     saveAnimate = appData.animate;
7734     if (clickType == Press) {
7735         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7736         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7737             // must be Edit Position mode with empty-square selected
7738             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7739             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7740             return;
7741         }
7742         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7743             return;
7744         }
7745         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7746             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7747         } else
7748         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7749         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7750           if(appData.sweepSelect) {
7751             promoSweep = defaultPromoChoice;
7752             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7753             selectFlag = 0; lastX = xPix; lastY = yPix;
7754             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7755             Sweep(0); // Pawn that is going to promote: preview promotion piece
7756             sweepSelecting = 1;
7757             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7758             MarkTargetSquares(1);
7759           }
7760           return; // promo popup appears on up-click
7761         }
7762         /* Finish clickclick move */
7763         if (appData.animate || appData.highlightLastMove) {
7764             SetHighlights(fromX, fromY, toX, toY);
7765         } else {
7766             ClearHighlights();
7767         }
7768     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7769         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7770         *promoRestrict = 0;
7771         if (appData.animate || appData.highlightLastMove) {
7772             SetHighlights(fromX, fromY, toX, toY);
7773         } else {
7774             ClearHighlights();
7775         }
7776     } else {
7777 #if 0
7778 // [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
7779         /* Finish drag move */
7780         if (appData.highlightLastMove) {
7781             SetHighlights(fromX, fromY, toX, toY);
7782         } else {
7783             ClearHighlights();
7784         }
7785 #endif
7786         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7787           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7788         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7789         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7790           dragging *= 2;            // flag button-less dragging if we are dragging
7791           MarkTargetSquares(1);
7792           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7793           else {
7794             kill2X = killX; kill2Y = killY;
7795             killX = x; killY = y;     //remeber this square as intermediate
7796             ReportClick("put", x, y); // and inform engine
7797             ReportClick("lift", x, y);
7798             MarkTargetSquares(0);
7799             return;
7800           }
7801         }
7802         DragPieceEnd(xPix, yPix); dragging = 0;
7803         /* Don't animate move and drag both */
7804         appData.animate = FALSE;
7805     }
7806
7807     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7808     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7809         ChessSquare piece = boards[currentMove][fromY][fromX];
7810         if(gameMode == EditPosition && piece != EmptySquare &&
7811            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7812             int n;
7813
7814             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7815                 n = PieceToNumber(piece - (int)BlackPawn);
7816                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7817                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7818                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7819             } else
7820             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7821                 n = PieceToNumber(piece);
7822                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7823                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7824                 boards[currentMove][n][BOARD_WIDTH-2]++;
7825             }
7826             boards[currentMove][fromY][fromX] = EmptySquare;
7827         }
7828         ClearHighlights();
7829         fromX = fromY = -1;
7830         MarkTargetSquares(1);
7831         DrawPosition(TRUE, boards[currentMove]);
7832         return;
7833     }
7834
7835     // off-board moves should not be highlighted
7836     if(x < 0 || y < 0) ClearHighlights();
7837     else ReportClick("put", x, y);
7838
7839     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7840
7841     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7842
7843     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7844         SetHighlights(fromX, fromY, toX, toY);
7845         MarkTargetSquares(1);
7846         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7847             // [HGM] super: promotion to captured piece selected from holdings
7848             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7849             promotionChoice = TRUE;
7850             // kludge follows to temporarily execute move on display, without promoting yet
7851             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7852             boards[currentMove][toY][toX] = p;
7853             DrawPosition(FALSE, boards[currentMove]);
7854             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7855             boards[currentMove][toY][toX] = q;
7856             DisplayMessage("Click in holdings to choose piece", "");
7857             return;
7858         }
7859         PromotionPopUp(promoChoice);
7860     } else {
7861         int oldMove = currentMove;
7862         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7863         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7864         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7865         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7866            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7867             DrawPosition(TRUE, boards[currentMove]);
7868         MarkTargetSquares(1);
7869         fromX = fromY = -1;
7870     }
7871     appData.animate = saveAnimate;
7872     if (appData.animate || appData.animateDragging) {
7873         /* Undo animation damage if needed */
7874         DrawPosition(FALSE, NULL);
7875     }
7876 }
7877
7878 int
7879 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7880 {   // front-end-free part taken out of PieceMenuPopup
7881     int whichMenu; int xSqr, ySqr;
7882
7883     if(seekGraphUp) { // [HGM] seekgraph
7884         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7885         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7886         return -2;
7887     }
7888
7889     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7890          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7891         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7892         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7893         if(action == Press)   {
7894             originalFlip = flipView;
7895             flipView = !flipView; // temporarily flip board to see game from partners perspective
7896             DrawPosition(TRUE, partnerBoard);
7897             DisplayMessage(partnerStatus, "");
7898             partnerUp = TRUE;
7899         } else if(action == Release) {
7900             flipView = originalFlip;
7901             DrawPosition(TRUE, boards[currentMove]);
7902             partnerUp = FALSE;
7903         }
7904         return -2;
7905     }
7906
7907     xSqr = EventToSquare(x, BOARD_WIDTH);
7908     ySqr = EventToSquare(y, BOARD_HEIGHT);
7909     if (action == Release) {
7910         if(pieceSweep != EmptySquare) {
7911             EditPositionMenuEvent(pieceSweep, toX, toY);
7912             pieceSweep = EmptySquare;
7913         } else UnLoadPV(); // [HGM] pv
7914     }
7915     if (action != Press) return -2; // return code to be ignored
7916     switch (gameMode) {
7917       case IcsExamining:
7918         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7919       case EditPosition:
7920         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7921         if (xSqr < 0 || ySqr < 0) return -1;
7922         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7923         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7924         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7925         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7926         NextPiece(0);
7927         return 2; // grab
7928       case IcsObserving:
7929         if(!appData.icsEngineAnalyze) return -1;
7930       case IcsPlayingWhite:
7931       case IcsPlayingBlack:
7932         if(!appData.zippyPlay) goto noZip;
7933       case AnalyzeMode:
7934       case AnalyzeFile:
7935       case MachinePlaysWhite:
7936       case MachinePlaysBlack:
7937       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7938         if (!appData.dropMenu) {
7939           LoadPV(x, y);
7940           return 2; // flag front-end to grab mouse events
7941         }
7942         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7943            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7944       case EditGame:
7945       noZip:
7946         if (xSqr < 0 || ySqr < 0) return -1;
7947         if (!appData.dropMenu || appData.testLegality &&
7948             gameInfo.variant != VariantBughouse &&
7949             gameInfo.variant != VariantCrazyhouse) return -1;
7950         whichMenu = 1; // drop menu
7951         break;
7952       default:
7953         return -1;
7954     }
7955
7956     if (((*fromX = xSqr) < 0) ||
7957         ((*fromY = ySqr) < 0)) {
7958         *fromX = *fromY = -1;
7959         return -1;
7960     }
7961     if (flipView)
7962       *fromX = BOARD_WIDTH - 1 - *fromX;
7963     else
7964       *fromY = BOARD_HEIGHT - 1 - *fromY;
7965
7966     return whichMenu;
7967 }
7968
7969 void
7970 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7971 {
7972 //    char * hint = lastHint;
7973     FrontEndProgramStats stats;
7974
7975     stats.which = cps == &first ? 0 : 1;
7976     stats.depth = cpstats->depth;
7977     stats.nodes = cpstats->nodes;
7978     stats.score = cpstats->score;
7979     stats.time = cpstats->time;
7980     stats.pv = cpstats->movelist;
7981     stats.hint = lastHint;
7982     stats.an_move_index = 0;
7983     stats.an_move_count = 0;
7984
7985     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7986         stats.hint = cpstats->move_name;
7987         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7988         stats.an_move_count = cpstats->nr_moves;
7989     }
7990
7991     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
7992
7993     SetProgramStats( &stats );
7994 }
7995
7996 void
7997 ClearEngineOutputPane (int which)
7998 {
7999     static FrontEndProgramStats dummyStats;
8000     dummyStats.which = which;
8001     dummyStats.pv = "#";
8002     SetProgramStats( &dummyStats );
8003 }
8004
8005 #define MAXPLAYERS 500
8006
8007 char *
8008 TourneyStandings (int display)
8009 {
8010     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8011     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8012     char result, *p, *names[MAXPLAYERS];
8013
8014     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8015         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8016     names[0] = p = strdup(appData.participants);
8017     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8018
8019     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8020
8021     while(result = appData.results[nr]) {
8022         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8023         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8024         wScore = bScore = 0;
8025         switch(result) {
8026           case '+': wScore = 2; break;
8027           case '-': bScore = 2; break;
8028           case '=': wScore = bScore = 1; break;
8029           case ' ':
8030           case '*': return strdup("busy"); // tourney not finished
8031         }
8032         score[w] += wScore;
8033         score[b] += bScore;
8034         games[w]++;
8035         games[b]++;
8036         nr++;
8037     }
8038     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8039     for(w=0; w<nPlayers; w++) {
8040         bScore = -1;
8041         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8042         ranking[w] = b; points[w] = bScore; score[b] = -2;
8043     }
8044     p = malloc(nPlayers*34+1);
8045     for(w=0; w<nPlayers && w<display; w++)
8046         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8047     free(names[0]);
8048     return p;
8049 }
8050
8051 void
8052 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8053 {       // count all piece types
8054         int p, f, r;
8055         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8056         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8057         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8058                 p = board[r][f];
8059                 pCnt[p]++;
8060                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8061                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8062                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8063                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8064                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8065                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8066         }
8067 }
8068
8069 int
8070 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8071 {
8072         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8073         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8074
8075         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8076         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8077         if(myPawns == 2 && nMine == 3) // KPP
8078             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8079         if(myPawns == 1 && nMine == 2) // KP
8080             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8081         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8082             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8083         if(myPawns) return FALSE;
8084         if(pCnt[WhiteRook+side])
8085             return pCnt[BlackRook-side] ||
8086                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8087                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8088                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8089         if(pCnt[WhiteCannon+side]) {
8090             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8091             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8092         }
8093         if(pCnt[WhiteKnight+side])
8094             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8095         return FALSE;
8096 }
8097
8098 int
8099 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8100 {
8101         VariantClass v = gameInfo.variant;
8102
8103         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8104         if(v == VariantShatranj) return TRUE; // always winnable through baring
8105         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8106         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8107
8108         if(v == VariantXiangqi) {
8109                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8110
8111                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8112                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8113                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8114                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8115                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8116                 if(stale) // we have at least one last-rank P plus perhaps C
8117                     return majors // KPKX
8118                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8119                 else // KCA*E*
8120                     return pCnt[WhiteFerz+side] // KCAK
8121                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8122                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8123                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8124
8125         } else if(v == VariantKnightmate) {
8126                 if(nMine == 1) return FALSE;
8127                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8128         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8129                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8130
8131                 if(nMine == 1) return FALSE; // bare King
8132                 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
8133                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8134                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8135                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8136                 if(pCnt[WhiteKnight+side])
8137                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8138                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8139                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8140                 if(nBishops)
8141                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8142                 if(pCnt[WhiteAlfil+side])
8143                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8144                 if(pCnt[WhiteWazir+side])
8145                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8146         }
8147
8148         return TRUE;
8149 }
8150
8151 int
8152 CompareWithRights (Board b1, Board b2)
8153 {
8154     int rights = 0;
8155     if(!CompareBoards(b1, b2)) return FALSE;
8156     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8157     /* compare castling rights */
8158     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8159            rights++; /* King lost rights, while rook still had them */
8160     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8161         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8162            rights++; /* but at least one rook lost them */
8163     }
8164     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8165            rights++;
8166     if( b1[CASTLING][5] != NoRights ) {
8167         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8168            rights++;
8169     }
8170     return rights == 0;
8171 }
8172
8173 int
8174 Adjudicate (ChessProgramState *cps)
8175 {       // [HGM] some adjudications useful with buggy engines
8176         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8177         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8178         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8179         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8180         int k, drop, count = 0; static int bare = 1;
8181         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8182         Boolean canAdjudicate = !appData.icsActive;
8183
8184         // most tests only when we understand the game, i.e. legality-checking on
8185             if( appData.testLegality )
8186             {   /* [HGM] Some more adjudications for obstinate engines */
8187                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8188                 static int moveCount = 6;
8189                 ChessMove result;
8190                 char *reason = NULL;
8191
8192                 /* Count what is on board. */
8193                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8194
8195                 /* Some material-based adjudications that have to be made before stalemate test */
8196                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8197                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8198                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8199                      if(canAdjudicate && appData.checkMates) {
8200                          if(engineOpponent)
8201                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8202                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8203                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8204                          return 1;
8205                      }
8206                 }
8207
8208                 /* Bare King in Shatranj (loses) or Losers (wins) */
8209                 if( nrW == 1 || nrB == 1) {
8210                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8211                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8212                      if(canAdjudicate && appData.checkMates) {
8213                          if(engineOpponent)
8214                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8215                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8216                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8217                          return 1;
8218                      }
8219                   } else
8220                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8221                   {    /* bare King */
8222                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8223                         if(canAdjudicate && appData.checkMates) {
8224                             /* but only adjudicate if adjudication enabled */
8225                             if(engineOpponent)
8226                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8227                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8228                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8229                             return 1;
8230                         }
8231                   }
8232                 } else bare = 1;
8233
8234
8235             // don't wait for engine to announce game end if we can judge ourselves
8236             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8237               case MT_CHECK:
8238                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8239                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8240                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8241                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8242                             checkCnt++;
8243                         if(checkCnt >= 2) {
8244                             reason = "Xboard adjudication: 3rd check";
8245                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8246                             break;
8247                         }
8248                     }
8249                 }
8250               case MT_NONE:
8251               default:
8252                 break;
8253               case MT_STEALMATE:
8254               case MT_STALEMATE:
8255               case MT_STAINMATE:
8256                 reason = "Xboard adjudication: Stalemate";
8257                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8258                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8259                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8260                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8261                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8262                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8263                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8264                                                                         EP_CHECKMATE : EP_WINS);
8265                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8266                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8267                 }
8268                 break;
8269               case MT_CHECKMATE:
8270                 reason = "Xboard adjudication: Checkmate";
8271                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8272                 if(gameInfo.variant == VariantShogi) {
8273                     if(forwardMostMove > backwardMostMove
8274                        && moveList[forwardMostMove-1][1] == '@'
8275                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8276                         reason = "XBoard adjudication: pawn-drop mate";
8277                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8278                     }
8279                 }
8280                 break;
8281             }
8282
8283                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8284                     case EP_STALEMATE:
8285                         result = GameIsDrawn; break;
8286                     case EP_CHECKMATE:
8287                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8288                     case EP_WINS:
8289                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8290                     default:
8291                         result = EndOfFile;
8292                 }
8293                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8294                     if(engineOpponent)
8295                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8296                     GameEnds( result, reason, GE_XBOARD );
8297                     return 1;
8298                 }
8299
8300                 /* Next absolutely insufficient mating material. */
8301                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8302                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8303                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8304
8305                      /* always flag draws, for judging claims */
8306                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8307
8308                      if(canAdjudicate && appData.materialDraws) {
8309                          /* but only adjudicate them if adjudication enabled */
8310                          if(engineOpponent) {
8311                            SendToProgram("force\n", engineOpponent); // suppress reply
8312                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8313                          }
8314                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8315                          return 1;
8316                      }
8317                 }
8318
8319                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8320                 if(gameInfo.variant == VariantXiangqi ?
8321                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8322                  : nrW + nrB == 4 &&
8323                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8324                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8325                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8326                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8327                    ) ) {
8328                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8329                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8330                           if(engineOpponent) {
8331                             SendToProgram("force\n", engineOpponent); // suppress reply
8332                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8333                           }
8334                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8335                           return 1;
8336                      }
8337                 } else moveCount = 6;
8338             }
8339
8340         // Repetition draws and 50-move rule can be applied independently of legality testing
8341
8342                 /* Check for rep-draws */
8343                 count = 0;
8344                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8345                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8346                 for(k = forwardMostMove-2;
8347                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8348                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8349                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8350                     k-=2)
8351                 {   int rights=0;
8352                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8353                         /* compare castling rights */
8354                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8355                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8356                                 rights++; /* King lost rights, while rook still had them */
8357                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8358                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8359                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8360                                    rights++; /* but at least one rook lost them */
8361                         }
8362                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8363                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8364                                 rights++;
8365                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8366                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8367                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8368                                    rights++;
8369                         }
8370                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8371                             && appData.drawRepeats > 1) {
8372                              /* adjudicate after user-specified nr of repeats */
8373                              int result = GameIsDrawn;
8374                              char *details = "XBoard adjudication: repetition draw";
8375                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8376                                 // [HGM] xiangqi: check for forbidden perpetuals
8377                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8378                                 for(m=forwardMostMove; m>k; m-=2) {
8379                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8380                                         ourPerpetual = 0; // the current mover did not always check
8381                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8382                                         hisPerpetual = 0; // the opponent did not always check
8383                                 }
8384                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8385                                                                         ourPerpetual, hisPerpetual);
8386                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8387                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8388                                     details = "Xboard adjudication: perpetual checking";
8389                                 } else
8390                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8391                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8392                                 } else
8393                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8394                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8395                                         result = BlackWins;
8396                                         details = "Xboard adjudication: repetition";
8397                                     }
8398                                 } else // it must be XQ
8399                                 // Now check for perpetual chases
8400                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8401                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8402                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8403                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8404                                         static char resdet[MSG_SIZ];
8405                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8406                                         details = resdet;
8407                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8408                                     } else
8409                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8410                                         break; // Abort repetition-checking loop.
8411                                 }
8412                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8413                              }
8414                              if(engineOpponent) {
8415                                SendToProgram("force\n", engineOpponent); // suppress reply
8416                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8417                              }
8418                              GameEnds( result, details, GE_XBOARD );
8419                              return 1;
8420                         }
8421                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8422                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8423                     }
8424                 }
8425
8426                 /* Now we test for 50-move draws. Determine ply count */
8427                 count = forwardMostMove;
8428                 /* look for last irreversble move */
8429                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8430                     count--;
8431                 /* if we hit starting position, add initial plies */
8432                 if( count == backwardMostMove )
8433                     count -= initialRulePlies;
8434                 count = forwardMostMove - count;
8435                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8436                         // adjust reversible move counter for checks in Xiangqi
8437                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8438                         if(i < backwardMostMove) i = backwardMostMove;
8439                         while(i <= forwardMostMove) {
8440                                 lastCheck = inCheck; // check evasion does not count
8441                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8442                                 if(inCheck || lastCheck) count--; // check does not count
8443                                 i++;
8444                         }
8445                 }
8446                 if( count >= 100)
8447                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8448                          /* this is used to judge if draw claims are legal */
8449                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8450                          if(engineOpponent) {
8451                            SendToProgram("force\n", engineOpponent); // suppress reply
8452                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8453                          }
8454                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8455                          return 1;
8456                 }
8457
8458                 /* if draw offer is pending, treat it as a draw claim
8459                  * when draw condition present, to allow engines a way to
8460                  * claim draws before making their move to avoid a race
8461                  * condition occurring after their move
8462                  */
8463                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8464                          char *p = NULL;
8465                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8466                              p = "Draw claim: 50-move rule";
8467                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8468                              p = "Draw claim: 3-fold repetition";
8469                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8470                              p = "Draw claim: insufficient mating material";
8471                          if( p != NULL && canAdjudicate) {
8472                              if(engineOpponent) {
8473                                SendToProgram("force\n", engineOpponent); // suppress reply
8474                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8475                              }
8476                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8477                              return 1;
8478                          }
8479                 }
8480
8481                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8482                     if(engineOpponent) {
8483                       SendToProgram("force\n", engineOpponent); // suppress reply
8484                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8485                     }
8486                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8487                     return 1;
8488                 }
8489         return 0;
8490 }
8491
8492 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8493 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8494 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8495
8496 static int
8497 BitbaseProbe ()
8498 {
8499     int pieces[10], squares[10], cnt=0, r, f, res;
8500     static int loaded;
8501     static PPROBE_EGBB probeBB;
8502     if(!appData.testLegality) return 10;
8503     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8504     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8505     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8506     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8507         ChessSquare piece = boards[forwardMostMove][r][f];
8508         int black = (piece >= BlackPawn);
8509         int type = piece - black*BlackPawn;
8510         if(piece == EmptySquare) continue;
8511         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8512         if(type == WhiteKing) type = WhiteQueen + 1;
8513         type = egbbCode[type];
8514         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8515         pieces[cnt] = type + black*6;
8516         if(++cnt > 5) return 11;
8517     }
8518     pieces[cnt] = squares[cnt] = 0;
8519     // probe EGBB
8520     if(loaded == 2) return 13; // loading failed before
8521     if(loaded == 0) {
8522         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8523         HMODULE lib;
8524         PLOAD_EGBB loadBB;
8525         loaded = 2; // prepare for failure
8526         if(!path) return 13; // no egbb installed
8527         strncpy(buf, path + 8, MSG_SIZ);
8528         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8529         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8530         lib = LoadLibrary(buf);
8531         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8532         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8533         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8534         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8535         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8536         loaded = 1; // success!
8537     }
8538     res = probeBB(forwardMostMove & 1, pieces, squares);
8539     return res > 0 ? 1 : res < 0 ? -1 : 0;
8540 }
8541
8542 char *
8543 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8544 {   // [HGM] book: this routine intercepts moves to simulate book replies
8545     char *bookHit = NULL;
8546
8547     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8548         char buf[MSG_SIZ];
8549         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8550         SendToProgram(buf, cps);
8551     }
8552     //first determine if the incoming move brings opponent into his book
8553     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8554         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8555     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8556     if(bookHit != NULL && !cps->bookSuspend) {
8557         // make sure opponent is not going to reply after receiving move to book position
8558         SendToProgram("force\n", cps);
8559         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8560     }
8561     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8562     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8563     // now arrange restart after book miss
8564     if(bookHit) {
8565         // after a book hit we never send 'go', and the code after the call to this routine
8566         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8567         char buf[MSG_SIZ], *move = bookHit;
8568         if(cps->useSAN) {
8569             int fromX, fromY, toX, toY;
8570             char promoChar;
8571             ChessMove moveType;
8572             move = buf + 30;
8573             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8574                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8575                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8576                                     PosFlags(forwardMostMove),
8577                                     fromY, fromX, toY, toX, promoChar, move);
8578             } else {
8579                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8580                 bookHit = NULL;
8581             }
8582         }
8583         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8584         SendToProgram(buf, cps);
8585         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8586     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8587         SendToProgram("go\n", cps);
8588         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8589     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8590         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8591             SendToProgram("go\n", cps);
8592         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8593     }
8594     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8595 }
8596
8597 int
8598 LoadError (char *errmess, ChessProgramState *cps)
8599 {   // unloads engine and switches back to -ncp mode if it was first
8600     if(cps->initDone) return FALSE;
8601     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8602     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8603     cps->pr = NoProc;
8604     if(cps == &first) {
8605         appData.noChessProgram = TRUE;
8606         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8607         gameMode = BeginningOfGame; ModeHighlight();
8608         SetNCPMode();
8609     }
8610     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8611     DisplayMessage("", ""); // erase waiting message
8612     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8613     return TRUE;
8614 }
8615
8616 char *savedMessage;
8617 ChessProgramState *savedState;
8618 void
8619 DeferredBookMove (void)
8620 {
8621         if(savedState->lastPing != savedState->lastPong)
8622                     ScheduleDelayedEvent(DeferredBookMove, 10);
8623         else
8624         HandleMachineMove(savedMessage, savedState);
8625 }
8626
8627 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8628 static ChessProgramState *stalledEngine;
8629 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8630
8631 void
8632 HandleMachineMove (char *message, ChessProgramState *cps)
8633 {
8634     static char firstLeg[20];
8635     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8636     char realname[MSG_SIZ];
8637     int fromX, fromY, toX, toY;
8638     ChessMove moveType;
8639     char promoChar, roar;
8640     char *p, *pv=buf1;
8641     int oldError;
8642     char *bookHit;
8643
8644     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8645         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8646         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8647             DisplayError(_("Invalid pairing from pairing engine"), 0);
8648             return;
8649         }
8650         pairingReceived = 1;
8651         NextMatchGame();
8652         return; // Skim the pairing messages here.
8653     }
8654
8655     oldError = cps->userError; cps->userError = 0;
8656
8657 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8658     /*
8659      * Kludge to ignore BEL characters
8660      */
8661     while (*message == '\007') message++;
8662
8663     /*
8664      * [HGM] engine debug message: ignore lines starting with '#' character
8665      */
8666     if(cps->debug && *message == '#') return;
8667
8668     /*
8669      * Look for book output
8670      */
8671     if (cps == &first && bookRequested) {
8672         if (message[0] == '\t' || message[0] == ' ') {
8673             /* Part of the book output is here; append it */
8674             strcat(bookOutput, message);
8675             strcat(bookOutput, "  \n");
8676             return;
8677         } else if (bookOutput[0] != NULLCHAR) {
8678             /* All of book output has arrived; display it */
8679             char *p = bookOutput;
8680             while (*p != NULLCHAR) {
8681                 if (*p == '\t') *p = ' ';
8682                 p++;
8683             }
8684             DisplayInformation(bookOutput);
8685             bookRequested = FALSE;
8686             /* Fall through to parse the current output */
8687         }
8688     }
8689
8690     /*
8691      * Look for machine move.
8692      */
8693     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8694         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8695     {
8696         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8697             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8698             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8699             stalledEngine = cps;
8700             if(appData.ponderNextMove) { // bring opponent out of ponder
8701                 if(gameMode == TwoMachinesPlay) {
8702                     if(cps->other->pause)
8703                         PauseEngine(cps->other);
8704                     else
8705                         SendToProgram("easy\n", cps->other);
8706                 }
8707             }
8708             StopClocks();
8709             return;
8710         }
8711
8712       if(cps->usePing) {
8713
8714         /* This method is only useful on engines that support ping */
8715         if(abortEngineThink) {
8716             if (appData.debugMode) {
8717                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8718             }
8719             SendToProgram("undo\n", cps);
8720             return;
8721         }
8722
8723         if (cps->lastPing != cps->lastPong) {
8724             /* Extra move from before last new; ignore */
8725             if (appData.debugMode) {
8726                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8727             }
8728           return;
8729         }
8730
8731       } else {
8732
8733         int machineWhite = FALSE;
8734
8735         switch (gameMode) {
8736           case BeginningOfGame:
8737             /* Extra move from before last reset; ignore */
8738             if (appData.debugMode) {
8739                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8740             }
8741             return;
8742
8743           case EndOfGame:
8744           case IcsIdle:
8745           default:
8746             /* Extra move after we tried to stop.  The mode test is
8747                not a reliable way of detecting this problem, but it's
8748                the best we can do on engines that don't support ping.
8749             */
8750             if (appData.debugMode) {
8751                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8752                         cps->which, gameMode);
8753             }
8754             SendToProgram("undo\n", cps);
8755             return;
8756
8757           case MachinePlaysWhite:
8758           case IcsPlayingWhite:
8759             machineWhite = TRUE;
8760             break;
8761
8762           case MachinePlaysBlack:
8763           case IcsPlayingBlack:
8764             machineWhite = FALSE;
8765             break;
8766
8767           case TwoMachinesPlay:
8768             machineWhite = (cps->twoMachinesColor[0] == 'w');
8769             break;
8770         }
8771         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8772             if (appData.debugMode) {
8773                 fprintf(debugFP,
8774                         "Ignoring move out of turn by %s, gameMode %d"
8775                         ", forwardMost %d\n",
8776                         cps->which, gameMode, forwardMostMove);
8777             }
8778             return;
8779         }
8780       }
8781
8782         if(cps->alphaRank) AlphaRank(machineMove, 4);
8783
8784         // [HGM] lion: (some very limited) support for Alien protocol
8785         killX = killY = kill2X = kill2Y = -1;
8786         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8787             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8788             return;
8789         }
8790         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8791             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8792             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8793         }
8794         if(firstLeg[0]) { // there was a previous leg;
8795             // only support case where same piece makes two step
8796             char buf[20], *p = machineMove+1, *q = buf+1, f;
8797             safeStrCpy(buf, machineMove, 20);
8798             while(isdigit(*q)) q++; // find start of to-square
8799             safeStrCpy(machineMove, firstLeg, 20);
8800             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8801             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8802             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8803             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8804             firstLeg[0] = NULLCHAR;
8805         }
8806
8807         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8808                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8809             /* Machine move could not be parsed; ignore it. */
8810           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8811                     machineMove, _(cps->which));
8812             DisplayMoveError(buf1);
8813             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8814                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8815             if (gameMode == TwoMachinesPlay) {
8816               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8817                        buf1, GE_XBOARD);
8818             }
8819             return;
8820         }
8821
8822         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8823         /* So we have to redo legality test with true e.p. status here,  */
8824         /* to make sure an illegal e.p. capture does not slip through,   */
8825         /* to cause a forfeit on a justified illegal-move complaint      */
8826         /* of the opponent.                                              */
8827         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8828            ChessMove moveType;
8829            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8830                              fromY, fromX, toY, toX, promoChar);
8831             if(moveType == IllegalMove) {
8832               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8833                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8834                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8835                            buf1, GE_XBOARD);
8836                 return;
8837            } else if(!appData.fischerCastling)
8838            /* [HGM] Kludge to handle engines that send FRC-style castling
8839               when they shouldn't (like TSCP-Gothic) */
8840            switch(moveType) {
8841              case WhiteASideCastleFR:
8842              case BlackASideCastleFR:
8843                toX+=2;
8844                currentMoveString[2]++;
8845                break;
8846              case WhiteHSideCastleFR:
8847              case BlackHSideCastleFR:
8848                toX--;
8849                currentMoveString[2]--;
8850                break;
8851              default: ; // nothing to do, but suppresses warning of pedantic compilers
8852            }
8853         }
8854         hintRequested = FALSE;
8855         lastHint[0] = NULLCHAR;
8856         bookRequested = FALSE;
8857         /* Program may be pondering now */
8858         cps->maybeThinking = TRUE;
8859         if (cps->sendTime == 2) cps->sendTime = 1;
8860         if (cps->offeredDraw) cps->offeredDraw--;
8861
8862         /* [AS] Save move info*/
8863         pvInfoList[ forwardMostMove ].score = programStats.score;
8864         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8865         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8866
8867         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8868
8869         /* Test suites abort the 'game' after one move */
8870         if(*appData.finger) {
8871            static FILE *f;
8872            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8873            if(!f) f = fopen(appData.finger, "w");
8874            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8875            else { DisplayFatalError("Bad output file", errno, 0); return; }
8876            free(fen);
8877            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8878         }
8879         if(appData.epd) {
8880            if(solvingTime >= 0) {
8881               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8882               totalTime += solvingTime; first.matchWins++;
8883            } else {
8884               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8885               second.matchWins++;
8886            }
8887            OutputKibitz(2, buf1);
8888            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8889         }
8890
8891         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8892         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8893             int count = 0;
8894
8895             while( count < adjudicateLossPlies ) {
8896                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8897
8898                 if( count & 1 ) {
8899                     score = -score; /* Flip score for winning side */
8900                 }
8901
8902                 if( score > appData.adjudicateLossThreshold ) {
8903                     break;
8904                 }
8905
8906                 count++;
8907             }
8908
8909             if( count >= adjudicateLossPlies ) {
8910                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8911
8912                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8913                     "Xboard adjudication",
8914                     GE_XBOARD );
8915
8916                 return;
8917             }
8918         }
8919
8920         if(Adjudicate(cps)) {
8921             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8922             return; // [HGM] adjudicate: for all automatic game ends
8923         }
8924
8925 #if ZIPPY
8926         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8927             first.initDone) {
8928           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8929                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8930                 SendToICS("draw ");
8931                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8932           }
8933           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8934           ics_user_moved = 1;
8935           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8936                 char buf[3*MSG_SIZ];
8937
8938                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8939                         programStats.score / 100.,
8940                         programStats.depth,
8941                         programStats.time / 100.,
8942                         (unsigned int)programStats.nodes,
8943                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8944                         programStats.movelist);
8945                 SendToICS(buf);
8946           }
8947         }
8948 #endif
8949
8950         /* [AS] Clear stats for next move */
8951         ClearProgramStats();
8952         thinkOutput[0] = NULLCHAR;
8953         hiddenThinkOutputState = 0;
8954
8955         bookHit = NULL;
8956         if (gameMode == TwoMachinesPlay) {
8957             /* [HGM] relaying draw offers moved to after reception of move */
8958             /* and interpreting offer as claim if it brings draw condition */
8959             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8960                 SendToProgram("draw\n", cps->other);
8961             }
8962             if (cps->other->sendTime) {
8963                 SendTimeRemaining(cps->other,
8964                                   cps->other->twoMachinesColor[0] == 'w');
8965             }
8966             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8967             if (firstMove && !bookHit) {
8968                 firstMove = FALSE;
8969                 if (cps->other->useColors) {
8970                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8971                 }
8972                 SendToProgram("go\n", cps->other);
8973             }
8974             cps->other->maybeThinking = TRUE;
8975         }
8976
8977         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8978
8979         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8980
8981         if (!pausing && appData.ringBellAfterMoves) {
8982             if(!roar) RingBell();
8983         }
8984
8985         /*
8986          * Reenable menu items that were disabled while
8987          * machine was thinking
8988          */
8989         if (gameMode != TwoMachinesPlay)
8990             SetUserThinkingEnables();
8991
8992         // [HGM] book: after book hit opponent has received move and is now in force mode
8993         // force the book reply into it, and then fake that it outputted this move by jumping
8994         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8995         if(bookHit) {
8996                 static char bookMove[MSG_SIZ]; // a bit generous?
8997
8998                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8999                 strcat(bookMove, bookHit);
9000                 message = bookMove;
9001                 cps = cps->other;
9002                 programStats.nodes = programStats.depth = programStats.time =
9003                 programStats.score = programStats.got_only_move = 0;
9004                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9005
9006                 if(cps->lastPing != cps->lastPong) {
9007                     savedMessage = message; // args for deferred call
9008                     savedState = cps;
9009                     ScheduleDelayedEvent(DeferredBookMove, 10);
9010                     return;
9011                 }
9012                 goto FakeBookMove;
9013         }
9014
9015         return;
9016     }
9017
9018     /* Set special modes for chess engines.  Later something general
9019      *  could be added here; for now there is just one kludge feature,
9020      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9021      *  when "xboard" is given as an interactive command.
9022      */
9023     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9024         cps->useSigint = FALSE;
9025         cps->useSigterm = FALSE;
9026     }
9027     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9028       ParseFeatures(message+8, cps);
9029       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9030     }
9031
9032     if (!strncmp(message, "setup ", 6) && 
9033         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9034           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9035                                         ) { // [HGM] allow first engine to define opening position
9036       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9037       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9038       *buf = NULLCHAR;
9039       if(sscanf(message, "setup (%s", buf) == 1) {
9040         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9041         ASSIGN(appData.pieceToCharTable, buf);
9042       }
9043       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9044       if(dummy >= 3) {
9045         while(message[s] && message[s++] != ' ');
9046         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9047            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9048             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9049             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9050           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9051           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9052           startedFromSetupPosition = FALSE;
9053         }
9054       }
9055       if(startedFromSetupPosition) return;
9056       ParseFEN(boards[0], &dummy, message+s, FALSE);
9057       DrawPosition(TRUE, boards[0]);
9058       CopyBoard(initialPosition, boards[0]);
9059       startedFromSetupPosition = TRUE;
9060       return;
9061     }
9062     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9063       ChessSquare piece = WhitePawn;
9064       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9065       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9066       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9067       piece += CharToPiece(ID & 255) - WhitePawn;
9068       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9069       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9070       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9071       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9072       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9073       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9074                                                && gameInfo.variant != VariantGreat
9075                                                && gameInfo.variant != VariantFairy    ) return;
9076       if(piece < EmptySquare) {
9077         pieceDefs = TRUE;
9078         ASSIGN(pieceDesc[piece], buf1);
9079         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9080       }
9081       return;
9082     }
9083     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9084       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9085       Sweep(0);
9086       return;
9087     }
9088     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9089      * want this, I was asked to put it in, and obliged.
9090      */
9091     if (!strncmp(message, "setboard ", 9)) {
9092         Board initial_position;
9093
9094         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9095
9096         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9097             DisplayError(_("Bad FEN received from engine"), 0);
9098             return ;
9099         } else {
9100            Reset(TRUE, FALSE);
9101            CopyBoard(boards[0], initial_position);
9102            initialRulePlies = FENrulePlies;
9103            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9104            else gameMode = MachinePlaysBlack;
9105            DrawPosition(FALSE, boards[currentMove]);
9106         }
9107         return;
9108     }
9109
9110     /*
9111      * Look for communication commands
9112      */
9113     if (!strncmp(message, "telluser ", 9)) {
9114         if(message[9] == '\\' && message[10] == '\\')
9115             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9116         PlayTellSound();
9117         DisplayNote(message + 9);
9118         return;
9119     }
9120     if (!strncmp(message, "tellusererror ", 14)) {
9121         cps->userError = 1;
9122         if(message[14] == '\\' && message[15] == '\\')
9123             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9124         PlayTellSound();
9125         DisplayError(message + 14, 0);
9126         return;
9127     }
9128     if (!strncmp(message, "tellopponent ", 13)) {
9129       if (appData.icsActive) {
9130         if (loggedOn) {
9131           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9132           SendToICS(buf1);
9133         }
9134       } else {
9135         DisplayNote(message + 13);
9136       }
9137       return;
9138     }
9139     if (!strncmp(message, "tellothers ", 11)) {
9140       if (appData.icsActive) {
9141         if (loggedOn) {
9142           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9143           SendToICS(buf1);
9144         }
9145       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9146       return;
9147     }
9148     if (!strncmp(message, "tellall ", 8)) {
9149       if (appData.icsActive) {
9150         if (loggedOn) {
9151           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9152           SendToICS(buf1);
9153         }
9154       } else {
9155         DisplayNote(message + 8);
9156       }
9157       return;
9158     }
9159     if (strncmp(message, "warning", 7) == 0) {
9160         /* Undocumented feature, use tellusererror in new code */
9161         DisplayError(message, 0);
9162         return;
9163     }
9164     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9165         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9166         strcat(realname, " query");
9167         AskQuestion(realname, buf2, buf1, cps->pr);
9168         return;
9169     }
9170     /* Commands from the engine directly to ICS.  We don't allow these to be
9171      *  sent until we are logged on. Crafty kibitzes have been known to
9172      *  interfere with the login process.
9173      */
9174     if (loggedOn) {
9175         if (!strncmp(message, "tellics ", 8)) {
9176             SendToICS(message + 8);
9177             SendToICS("\n");
9178             return;
9179         }
9180         if (!strncmp(message, "tellicsnoalias ", 15)) {
9181             SendToICS(ics_prefix);
9182             SendToICS(message + 15);
9183             SendToICS("\n");
9184             return;
9185         }
9186         /* The following are for backward compatibility only */
9187         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9188             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9189             SendToICS(ics_prefix);
9190             SendToICS(message);
9191             SendToICS("\n");
9192             return;
9193         }
9194     }
9195     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9196         if(initPing == cps->lastPong) {
9197             if(gameInfo.variant == VariantUnknown) {
9198                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9199                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9200                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9201             }
9202             initPing = -1;
9203         }
9204         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9205             abortEngineThink = FALSE;
9206             DisplayMessage("", "");
9207             ThawUI();
9208         }
9209         return;
9210     }
9211     if(!strncmp(message, "highlight ", 10)) {
9212         if(appData.testLegality && !*engineVariant && appData.markers) return;
9213         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9214         return;
9215     }
9216     if(!strncmp(message, "click ", 6)) {
9217         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9218         if(appData.testLegality || !appData.oneClick) return;
9219         sscanf(message+6, "%c%d%c", &f, &y, &c);
9220         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9221         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9222         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9223         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9224         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9225         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9226             LeftClick(Release, lastLeftX, lastLeftY);
9227         controlKey  = (c == ',');
9228         LeftClick(Press, x, y);
9229         LeftClick(Release, x, y);
9230         first.highlight = f;
9231         return;
9232     }
9233     /*
9234      * If the move is illegal, cancel it and redraw the board.
9235      * Also deal with other error cases.  Matching is rather loose
9236      * here to accommodate engines written before the spec.
9237      */
9238     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9239         strncmp(message, "Error", 5) == 0) {
9240         if (StrStr(message, "name") ||
9241             StrStr(message, "rating") || StrStr(message, "?") ||
9242             StrStr(message, "result") || StrStr(message, "board") ||
9243             StrStr(message, "bk") || StrStr(message, "computer") ||
9244             StrStr(message, "variant") || StrStr(message, "hint") ||
9245             StrStr(message, "random") || StrStr(message, "depth") ||
9246             StrStr(message, "accepted")) {
9247             return;
9248         }
9249         if (StrStr(message, "protover")) {
9250           /* Program is responding to input, so it's apparently done
9251              initializing, and this error message indicates it is
9252              protocol version 1.  So we don't need to wait any longer
9253              for it to initialize and send feature commands. */
9254           FeatureDone(cps, 1);
9255           cps->protocolVersion = 1;
9256           return;
9257         }
9258         cps->maybeThinking = FALSE;
9259
9260         if (StrStr(message, "draw")) {
9261             /* Program doesn't have "draw" command */
9262             cps->sendDrawOffers = 0;
9263             return;
9264         }
9265         if (cps->sendTime != 1 &&
9266             (StrStr(message, "time") || StrStr(message, "otim"))) {
9267           /* Program apparently doesn't have "time" or "otim" command */
9268           cps->sendTime = 0;
9269           return;
9270         }
9271         if (StrStr(message, "analyze")) {
9272             cps->analysisSupport = FALSE;
9273             cps->analyzing = FALSE;
9274 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9275             EditGameEvent(); // [HGM] try to preserve loaded game
9276             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9277             DisplayError(buf2, 0);
9278             return;
9279         }
9280         if (StrStr(message, "(no matching move)st")) {
9281           /* Special kludge for GNU Chess 4 only */
9282           cps->stKludge = TRUE;
9283           SendTimeControl(cps, movesPerSession, timeControl,
9284                           timeIncrement, appData.searchDepth,
9285                           searchTime);
9286           return;
9287         }
9288         if (StrStr(message, "(no matching move)sd")) {
9289           /* Special kludge for GNU Chess 4 only */
9290           cps->sdKludge = TRUE;
9291           SendTimeControl(cps, movesPerSession, timeControl,
9292                           timeIncrement, appData.searchDepth,
9293                           searchTime);
9294           return;
9295         }
9296         if (!StrStr(message, "llegal")) {
9297             return;
9298         }
9299         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9300             gameMode == IcsIdle) return;
9301         if (forwardMostMove <= backwardMostMove) return;
9302         if (pausing) PauseEvent();
9303       if(appData.forceIllegal) {
9304             // [HGM] illegal: machine refused move; force position after move into it
9305           SendToProgram("force\n", cps);
9306           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9307                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9308                 // when black is to move, while there might be nothing on a2 or black
9309                 // might already have the move. So send the board as if white has the move.
9310                 // But first we must change the stm of the engine, as it refused the last move
9311                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9312                 if(WhiteOnMove(forwardMostMove)) {
9313                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9314                     SendBoard(cps, forwardMostMove); // kludgeless board
9315                 } else {
9316                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9317                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9318                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9319                 }
9320           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9321             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9322                  gameMode == TwoMachinesPlay)
9323               SendToProgram("go\n", cps);
9324             return;
9325       } else
9326         if (gameMode == PlayFromGameFile) {
9327             /* Stop reading this game file */
9328             gameMode = EditGame;
9329             ModeHighlight();
9330         }
9331         /* [HGM] illegal-move claim should forfeit game when Xboard */
9332         /* only passes fully legal moves                            */
9333         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9334             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9335                                 "False illegal-move claim", GE_XBOARD );
9336             return; // do not take back move we tested as valid
9337         }
9338         currentMove = forwardMostMove-1;
9339         DisplayMove(currentMove-1); /* before DisplayMoveError */
9340         SwitchClocks(forwardMostMove-1); // [HGM] race
9341         DisplayBothClocks();
9342         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9343                 parseList[currentMove], _(cps->which));
9344         DisplayMoveError(buf1);
9345         DrawPosition(FALSE, boards[currentMove]);
9346
9347         SetUserThinkingEnables();
9348         return;
9349     }
9350     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9351         /* Program has a broken "time" command that
9352            outputs a string not ending in newline.
9353            Don't use it. */
9354         cps->sendTime = 0;
9355     }
9356     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9357         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9358             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9359     }
9360
9361     /*
9362      * If chess program startup fails, exit with an error message.
9363      * Attempts to recover here are futile. [HGM] Well, we try anyway
9364      */
9365     if ((StrStr(message, "unknown host") != NULL)
9366         || (StrStr(message, "No remote directory") != NULL)
9367         || (StrStr(message, "not found") != NULL)
9368         || (StrStr(message, "No such file") != NULL)
9369         || (StrStr(message, "can't alloc") != NULL)
9370         || (StrStr(message, "Permission denied") != NULL)) {
9371
9372         cps->maybeThinking = FALSE;
9373         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9374                 _(cps->which), cps->program, cps->host, message);
9375         RemoveInputSource(cps->isr);
9376         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9377             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9378             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9379         }
9380         return;
9381     }
9382
9383     /*
9384      * Look for hint output
9385      */
9386     if (sscanf(message, "Hint: %s", buf1) == 1) {
9387         if (cps == &first && hintRequested) {
9388             hintRequested = FALSE;
9389             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9390                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9391                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9392                                     PosFlags(forwardMostMove),
9393                                     fromY, fromX, toY, toX, promoChar, buf1);
9394                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9395                 DisplayInformation(buf2);
9396             } else {
9397                 /* Hint move could not be parsed!? */
9398               snprintf(buf2, sizeof(buf2),
9399                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9400                         buf1, _(cps->which));
9401                 DisplayError(buf2, 0);
9402             }
9403         } else {
9404           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9405         }
9406         return;
9407     }
9408
9409     /*
9410      * Ignore other messages if game is not in progress
9411      */
9412     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9413         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9414
9415     /*
9416      * look for win, lose, draw, or draw offer
9417      */
9418     if (strncmp(message, "1-0", 3) == 0) {
9419         char *p, *q, *r = "";
9420         p = strchr(message, '{');
9421         if (p) {
9422             q = strchr(p, '}');
9423             if (q) {
9424                 *q = NULLCHAR;
9425                 r = p + 1;
9426             }
9427         }
9428         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9429         return;
9430     } else if (strncmp(message, "0-1", 3) == 0) {
9431         char *p, *q, *r = "";
9432         p = strchr(message, '{');
9433         if (p) {
9434             q = strchr(p, '}');
9435             if (q) {
9436                 *q = NULLCHAR;
9437                 r = p + 1;
9438             }
9439         }
9440         /* Kludge for Arasan 4.1 bug */
9441         if (strcmp(r, "Black resigns") == 0) {
9442             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9443             return;
9444         }
9445         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9446         return;
9447     } else if (strncmp(message, "1/2", 3) == 0) {
9448         char *p, *q, *r = "";
9449         p = strchr(message, '{');
9450         if (p) {
9451             q = strchr(p, '}');
9452             if (q) {
9453                 *q = NULLCHAR;
9454                 r = p + 1;
9455             }
9456         }
9457
9458         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9459         return;
9460
9461     } else if (strncmp(message, "White resign", 12) == 0) {
9462         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9463         return;
9464     } else if (strncmp(message, "Black resign", 12) == 0) {
9465         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9466         return;
9467     } else if (strncmp(message, "White matches", 13) == 0 ||
9468                strncmp(message, "Black matches", 13) == 0   ) {
9469         /* [HGM] ignore GNUShogi noises */
9470         return;
9471     } else if (strncmp(message, "White", 5) == 0 &&
9472                message[5] != '(' &&
9473                StrStr(message, "Black") == NULL) {
9474         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9475         return;
9476     } else if (strncmp(message, "Black", 5) == 0 &&
9477                message[5] != '(') {
9478         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9479         return;
9480     } else if (strcmp(message, "resign") == 0 ||
9481                strcmp(message, "computer resigns") == 0) {
9482         switch (gameMode) {
9483           case MachinePlaysBlack:
9484           case IcsPlayingBlack:
9485             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9486             break;
9487           case MachinePlaysWhite:
9488           case IcsPlayingWhite:
9489             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9490             break;
9491           case TwoMachinesPlay:
9492             if (cps->twoMachinesColor[0] == 'w')
9493               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9494             else
9495               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9496             break;
9497           default:
9498             /* can't happen */
9499             break;
9500         }
9501         return;
9502     } else if (strncmp(message, "opponent mates", 14) == 0) {
9503         switch (gameMode) {
9504           case MachinePlaysBlack:
9505           case IcsPlayingBlack:
9506             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9507             break;
9508           case MachinePlaysWhite:
9509           case IcsPlayingWhite:
9510             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9511             break;
9512           case TwoMachinesPlay:
9513             if (cps->twoMachinesColor[0] == 'w')
9514               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9515             else
9516               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9517             break;
9518           default:
9519             /* can't happen */
9520             break;
9521         }
9522         return;
9523     } else if (strncmp(message, "computer mates", 14) == 0) {
9524         switch (gameMode) {
9525           case MachinePlaysBlack:
9526           case IcsPlayingBlack:
9527             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9528             break;
9529           case MachinePlaysWhite:
9530           case IcsPlayingWhite:
9531             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9532             break;
9533           case TwoMachinesPlay:
9534             if (cps->twoMachinesColor[0] == 'w')
9535               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9536             else
9537               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9538             break;
9539           default:
9540             /* can't happen */
9541             break;
9542         }
9543         return;
9544     } else if (strncmp(message, "checkmate", 9) == 0) {
9545         if (WhiteOnMove(forwardMostMove)) {
9546             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9547         } else {
9548             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9549         }
9550         return;
9551     } else if (strstr(message, "Draw") != NULL ||
9552                strstr(message, "game is a draw") != NULL) {
9553         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9554         return;
9555     } else if (strstr(message, "offer") != NULL &&
9556                strstr(message, "draw") != NULL) {
9557 #if ZIPPY
9558         if (appData.zippyPlay && first.initDone) {
9559             /* Relay offer to ICS */
9560             SendToICS(ics_prefix);
9561             SendToICS("draw\n");
9562         }
9563 #endif
9564         cps->offeredDraw = 2; /* valid until this engine moves twice */
9565         if (gameMode == TwoMachinesPlay) {
9566             if (cps->other->offeredDraw) {
9567                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9568             /* [HGM] in two-machine mode we delay relaying draw offer      */
9569             /* until after we also have move, to see if it is really claim */
9570             }
9571         } else if (gameMode == MachinePlaysWhite ||
9572                    gameMode == MachinePlaysBlack) {
9573           if (userOfferedDraw) {
9574             DisplayInformation(_("Machine accepts your draw offer"));
9575             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9576           } else {
9577             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9578           }
9579         }
9580     }
9581
9582
9583     /*
9584      * Look for thinking output
9585      */
9586     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9587           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9588                                 ) {
9589         int plylev, mvleft, mvtot, curscore, time;
9590         char mvname[MOVE_LEN];
9591         u64 nodes; // [DM]
9592         char plyext;
9593         int ignore = FALSE;
9594         int prefixHint = FALSE;
9595         mvname[0] = NULLCHAR;
9596
9597         switch (gameMode) {
9598           case MachinePlaysBlack:
9599           case IcsPlayingBlack:
9600             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9601             break;
9602           case MachinePlaysWhite:
9603           case IcsPlayingWhite:
9604             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9605             break;
9606           case AnalyzeMode:
9607           case AnalyzeFile:
9608             break;
9609           case IcsObserving: /* [DM] icsEngineAnalyze */
9610             if (!appData.icsEngineAnalyze) ignore = TRUE;
9611             break;
9612           case TwoMachinesPlay:
9613             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9614                 ignore = TRUE;
9615             }
9616             break;
9617           default:
9618             ignore = TRUE;
9619             break;
9620         }
9621
9622         if (!ignore) {
9623             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9624             buf1[0] = NULLCHAR;
9625             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9626                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9627                 char score_buf[MSG_SIZ];
9628
9629                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9630                     nodes += u64Const(0x100000000);
9631
9632                 if (plyext != ' ' && plyext != '\t') {
9633                     time *= 100;
9634                 }
9635
9636                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9637                 if( cps->scoreIsAbsolute &&
9638                     ( gameMode == MachinePlaysBlack ||
9639                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9640                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9641                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9642                      !WhiteOnMove(currentMove)
9643                     ) )
9644                 {
9645                     curscore = -curscore;
9646                 }
9647
9648                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9649
9650                 if(*bestMove) { // rememer time best EPD move was first found
9651                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9652                     ChessMove mt;
9653                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9654                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9655                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9656                 }
9657
9658                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9659                         char buf[MSG_SIZ];
9660                         FILE *f;
9661                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9662                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9663                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9664                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9665                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9666                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9667                                 fclose(f);
9668                         }
9669                         else
9670                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9671                           DisplayError(_("failed writing PV"), 0);
9672                 }
9673
9674                 tempStats.depth = plylev;
9675                 tempStats.nodes = nodes;
9676                 tempStats.time = time;
9677                 tempStats.score = curscore;
9678                 tempStats.got_only_move = 0;
9679
9680                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9681                         int ticklen;
9682
9683                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9684                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9685                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9686                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9687                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9688                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9689                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9690                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9691                 }
9692
9693                 /* Buffer overflow protection */
9694                 if (pv[0] != NULLCHAR) {
9695                     if (strlen(pv) >= sizeof(tempStats.movelist)
9696                         && appData.debugMode) {
9697                         fprintf(debugFP,
9698                                 "PV is too long; using the first %u bytes.\n",
9699                                 (unsigned) sizeof(tempStats.movelist) - 1);
9700                     }
9701
9702                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9703                 } else {
9704                     sprintf(tempStats.movelist, " no PV\n");
9705                 }
9706
9707                 if (tempStats.seen_stat) {
9708                     tempStats.ok_to_send = 1;
9709                 }
9710
9711                 if (strchr(tempStats.movelist, '(') != NULL) {
9712                     tempStats.line_is_book = 1;
9713                     tempStats.nr_moves = 0;
9714                     tempStats.moves_left = 0;
9715                 } else {
9716                     tempStats.line_is_book = 0;
9717                 }
9718
9719                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9720                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9721
9722                 SendProgramStatsToFrontend( cps, &tempStats );
9723
9724                 /*
9725                     [AS] Protect the thinkOutput buffer from overflow... this
9726                     is only useful if buf1 hasn't overflowed first!
9727                 */
9728                 if(curscore >= MATE_SCORE) 
9729                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9730                 else if(curscore <= -MATE_SCORE) 
9731                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9732                 else
9733                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9734                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9735                          plylev,
9736                          (gameMode == TwoMachinesPlay ?
9737                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9738                          score_buf,
9739                          prefixHint ? lastHint : "",
9740                          prefixHint ? " " : "" );
9741
9742                 if( buf1[0] != NULLCHAR ) {
9743                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9744
9745                     if( strlen(pv) > max_len ) {
9746                         if( appData.debugMode) {
9747                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9748                         }
9749                         pv[max_len+1] = '\0';
9750                     }
9751
9752                     strcat( thinkOutput, pv);
9753                 }
9754
9755                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9756                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9757                     DisplayMove(currentMove - 1);
9758                 }
9759                 return;
9760
9761             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9762                 /* crafty (9.25+) says "(only move) <move>"
9763                  * if there is only 1 legal move
9764                  */
9765                 sscanf(p, "(only move) %s", buf1);
9766                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9767                 sprintf(programStats.movelist, "%s (only move)", buf1);
9768                 programStats.depth = 1;
9769                 programStats.nr_moves = 1;
9770                 programStats.moves_left = 1;
9771                 programStats.nodes = 1;
9772                 programStats.time = 1;
9773                 programStats.got_only_move = 1;
9774
9775                 /* Not really, but we also use this member to
9776                    mean "line isn't going to change" (Crafty
9777                    isn't searching, so stats won't change) */
9778                 programStats.line_is_book = 1;
9779
9780                 SendProgramStatsToFrontend( cps, &programStats );
9781
9782                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9783                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9784                     DisplayMove(currentMove - 1);
9785                 }
9786                 return;
9787             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9788                               &time, &nodes, &plylev, &mvleft,
9789                               &mvtot, mvname) >= 5) {
9790                 /* The stat01: line is from Crafty (9.29+) in response
9791                    to the "." command */
9792                 programStats.seen_stat = 1;
9793                 cps->maybeThinking = TRUE;
9794
9795                 if (programStats.got_only_move || !appData.periodicUpdates)
9796                   return;
9797
9798                 programStats.depth = plylev;
9799                 programStats.time = time;
9800                 programStats.nodes = nodes;
9801                 programStats.moves_left = mvleft;
9802                 programStats.nr_moves = mvtot;
9803                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9804                 programStats.ok_to_send = 1;
9805                 programStats.movelist[0] = '\0';
9806
9807                 SendProgramStatsToFrontend( cps, &programStats );
9808
9809                 return;
9810
9811             } else if (strncmp(message,"++",2) == 0) {
9812                 /* Crafty 9.29+ outputs this */
9813                 programStats.got_fail = 2;
9814                 return;
9815
9816             } else if (strncmp(message,"--",2) == 0) {
9817                 /* Crafty 9.29+ outputs this */
9818                 programStats.got_fail = 1;
9819                 return;
9820
9821             } else if (thinkOutput[0] != NULLCHAR &&
9822                        strncmp(message, "    ", 4) == 0) {
9823                 unsigned message_len;
9824
9825                 p = message;
9826                 while (*p && *p == ' ') p++;
9827
9828                 message_len = strlen( p );
9829
9830                 /* [AS] Avoid buffer overflow */
9831                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9832                     strcat(thinkOutput, " ");
9833                     strcat(thinkOutput, p);
9834                 }
9835
9836                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9837                     strcat(programStats.movelist, " ");
9838                     strcat(programStats.movelist, p);
9839                 }
9840
9841                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9842                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9843                     DisplayMove(currentMove - 1);
9844                 }
9845                 return;
9846             }
9847         }
9848         else {
9849             buf1[0] = NULLCHAR;
9850
9851             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9852                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9853             {
9854                 ChessProgramStats cpstats;
9855
9856                 if (plyext != ' ' && plyext != '\t') {
9857                     time *= 100;
9858                 }
9859
9860                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9861                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9862                     curscore = -curscore;
9863                 }
9864
9865                 cpstats.depth = plylev;
9866                 cpstats.nodes = nodes;
9867                 cpstats.time = time;
9868                 cpstats.score = curscore;
9869                 cpstats.got_only_move = 0;
9870                 cpstats.movelist[0] = '\0';
9871
9872                 if (buf1[0] != NULLCHAR) {
9873                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9874                 }
9875
9876                 cpstats.ok_to_send = 0;
9877                 cpstats.line_is_book = 0;
9878                 cpstats.nr_moves = 0;
9879                 cpstats.moves_left = 0;
9880
9881                 SendProgramStatsToFrontend( cps, &cpstats );
9882             }
9883         }
9884     }
9885 }
9886
9887
9888 /* Parse a game score from the character string "game", and
9889    record it as the history of the current game.  The game
9890    score is NOT assumed to start from the standard position.
9891    The display is not updated in any way.
9892    */
9893 void
9894 ParseGameHistory (char *game)
9895 {
9896     ChessMove moveType;
9897     int fromX, fromY, toX, toY, boardIndex;
9898     char promoChar;
9899     char *p, *q;
9900     char buf[MSG_SIZ];
9901
9902     if (appData.debugMode)
9903       fprintf(debugFP, "Parsing game history: %s\n", game);
9904
9905     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9906     gameInfo.site = StrSave(appData.icsHost);
9907     gameInfo.date = PGNDate();
9908     gameInfo.round = StrSave("-");
9909
9910     /* Parse out names of players */
9911     while (*game == ' ') game++;
9912     p = buf;
9913     while (*game != ' ') *p++ = *game++;
9914     *p = NULLCHAR;
9915     gameInfo.white = StrSave(buf);
9916     while (*game == ' ') game++;
9917     p = buf;
9918     while (*game != ' ' && *game != '\n') *p++ = *game++;
9919     *p = NULLCHAR;
9920     gameInfo.black = StrSave(buf);
9921
9922     /* Parse moves */
9923     boardIndex = blackPlaysFirst ? 1 : 0;
9924     yynewstr(game);
9925     for (;;) {
9926         yyboardindex = boardIndex;
9927         moveType = (ChessMove) Myylex();
9928         switch (moveType) {
9929           case IllegalMove:             /* maybe suicide chess, etc. */
9930   if (appData.debugMode) {
9931     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9932     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9933     setbuf(debugFP, NULL);
9934   }
9935           case WhitePromotion:
9936           case BlackPromotion:
9937           case WhiteNonPromotion:
9938           case BlackNonPromotion:
9939           case NormalMove:
9940           case FirstLeg:
9941           case WhiteCapturesEnPassant:
9942           case BlackCapturesEnPassant:
9943           case WhiteKingSideCastle:
9944           case WhiteQueenSideCastle:
9945           case BlackKingSideCastle:
9946           case BlackQueenSideCastle:
9947           case WhiteKingSideCastleWild:
9948           case WhiteQueenSideCastleWild:
9949           case BlackKingSideCastleWild:
9950           case BlackQueenSideCastleWild:
9951           /* PUSH Fabien */
9952           case WhiteHSideCastleFR:
9953           case WhiteASideCastleFR:
9954           case BlackHSideCastleFR:
9955           case BlackASideCastleFR:
9956           /* POP Fabien */
9957             fromX = currentMoveString[0] - AAA;
9958             fromY = currentMoveString[1] - ONE;
9959             toX = currentMoveString[2] - AAA;
9960             toY = currentMoveString[3] - ONE;
9961             promoChar = currentMoveString[4];
9962             break;
9963           case WhiteDrop:
9964           case BlackDrop:
9965             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9966             fromX = moveType == WhiteDrop ?
9967               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9968             (int) CharToPiece(ToLower(currentMoveString[0]));
9969             fromY = DROP_RANK;
9970             toX = currentMoveString[2] - AAA;
9971             toY = currentMoveString[3] - ONE;
9972             promoChar = NULLCHAR;
9973             break;
9974           case AmbiguousMove:
9975             /* bug? */
9976             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9977   if (appData.debugMode) {
9978     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9979     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9980     setbuf(debugFP, NULL);
9981   }
9982             DisplayError(buf, 0);
9983             return;
9984           case ImpossibleMove:
9985             /* bug? */
9986             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9987   if (appData.debugMode) {
9988     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9989     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9990     setbuf(debugFP, NULL);
9991   }
9992             DisplayError(buf, 0);
9993             return;
9994           case EndOfFile:
9995             if (boardIndex < backwardMostMove) {
9996                 /* Oops, gap.  How did that happen? */
9997                 DisplayError(_("Gap in move list"), 0);
9998                 return;
9999             }
10000             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10001             if (boardIndex > forwardMostMove) {
10002                 forwardMostMove = boardIndex;
10003             }
10004             return;
10005           case ElapsedTime:
10006             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10007                 strcat(parseList[boardIndex-1], " ");
10008                 strcat(parseList[boardIndex-1], yy_text);
10009             }
10010             continue;
10011           case Comment:
10012           case PGNTag:
10013           case NAG:
10014           default:
10015             /* ignore */
10016             continue;
10017           case WhiteWins:
10018           case BlackWins:
10019           case GameIsDrawn:
10020           case GameUnfinished:
10021             if (gameMode == IcsExamining) {
10022                 if (boardIndex < backwardMostMove) {
10023                     /* Oops, gap.  How did that happen? */
10024                     return;
10025                 }
10026                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10027                 return;
10028             }
10029             gameInfo.result = moveType;
10030             p = strchr(yy_text, '{');
10031             if (p == NULL) p = strchr(yy_text, '(');
10032             if (p == NULL) {
10033                 p = yy_text;
10034                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10035             } else {
10036                 q = strchr(p, *p == '{' ? '}' : ')');
10037                 if (q != NULL) *q = NULLCHAR;
10038                 p++;
10039             }
10040             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10041             gameInfo.resultDetails = StrSave(p);
10042             continue;
10043         }
10044         if (boardIndex >= forwardMostMove &&
10045             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10046             backwardMostMove = blackPlaysFirst ? 1 : 0;
10047             return;
10048         }
10049         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10050                                  fromY, fromX, toY, toX, promoChar,
10051                                  parseList[boardIndex]);
10052         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10053         /* currentMoveString is set as a side-effect of yylex */
10054         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10055         strcat(moveList[boardIndex], "\n");
10056         boardIndex++;
10057         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10058         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10059           case MT_NONE:
10060           case MT_STALEMATE:
10061           default:
10062             break;
10063           case MT_CHECK:
10064             if(!IS_SHOGI(gameInfo.variant))
10065                 strcat(parseList[boardIndex - 1], "+");
10066             break;
10067           case MT_CHECKMATE:
10068           case MT_STAINMATE:
10069             strcat(parseList[boardIndex - 1], "#");
10070             break;
10071         }
10072     }
10073 }
10074
10075
10076 /* Apply a move to the given board  */
10077 void
10078 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10079 {
10080   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10081   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10082
10083     /* [HGM] compute & store e.p. status and castling rights for new position */
10084     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10085
10086       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10087       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10088       board[EP_STATUS] = EP_NONE;
10089       board[EP_FILE] = board[EP_RANK] = 100;
10090
10091   if (fromY == DROP_RANK) {
10092         /* must be first */
10093         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10094             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10095             return;
10096         }
10097         piece = board[toY][toX] = (ChessSquare) fromX;
10098   } else {
10099 //      ChessSquare victim;
10100       int i;
10101
10102       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10103 //           victim = board[killY][killX],
10104            killed = board[killY][killX],
10105            board[killY][killX] = EmptySquare,
10106            board[EP_STATUS] = EP_CAPTURE;
10107            if( kill2X >= 0 && kill2Y >= 0)
10108              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10109       }
10110
10111       if( board[toY][toX] != EmptySquare ) {
10112            board[EP_STATUS] = EP_CAPTURE;
10113            if( (fromX != toX || fromY != toY) && // not igui!
10114                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10115                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10116                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10117            }
10118       }
10119
10120       pawn = board[fromY][fromX];
10121       if( pawn == WhiteLance || pawn == BlackLance ) {
10122            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10123                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10124                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10125            }
10126       }
10127       if( pawn == WhitePawn ) {
10128            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10129                board[EP_STATUS] = EP_PAWN_MOVE;
10130            if( toY-fromY>=2) {
10131                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10132                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10133                         gameInfo.variant != VariantBerolina || toX < fromX)
10134                       board[EP_STATUS] = toX | berolina;
10135                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10136                         gameInfo.variant != VariantBerolina || toX > fromX)
10137                       board[EP_STATUS] = toX;
10138            }
10139       } else
10140       if( pawn == BlackPawn ) {
10141            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10142                board[EP_STATUS] = EP_PAWN_MOVE;
10143            if( toY-fromY<= -2) {
10144                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10145                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10146                         gameInfo.variant != VariantBerolina || toX < fromX)
10147                       board[EP_STATUS] = toX | berolina;
10148                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10149                         gameInfo.variant != VariantBerolina || toX > fromX)
10150                       board[EP_STATUS] = toX;
10151            }
10152        }
10153
10154        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10155        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10156        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10157        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10158
10159        for(i=0; i<nrCastlingRights; i++) {
10160            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10161               board[CASTLING][i] == toX   && castlingRank[i] == toY
10162              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10163        }
10164
10165        if(gameInfo.variant == VariantSChess) { // update virginity
10166            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10167            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10168            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10169            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10170        }
10171
10172      if (fromX == toX && fromY == toY) return;
10173
10174      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10175      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10176      if(gameInfo.variant == VariantKnightmate)
10177          king += (int) WhiteUnicorn - (int) WhiteKing;
10178
10179     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10180        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10181         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10182         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10183         board[EP_STATUS] = EP_NONE; // capture was fake!
10184     } else
10185     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10186         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10187         board[toY][toX] = piece;
10188         board[EP_STATUS] = EP_NONE; // capture was fake!
10189     } else
10190     /* Code added by Tord: */
10191     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10192     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10193         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10194       board[EP_STATUS] = EP_NONE; // capture was fake!
10195       board[fromY][fromX] = EmptySquare;
10196       board[toY][toX] = EmptySquare;
10197       if((toX > fromX) != (piece == WhiteRook)) {
10198         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10199       } else {
10200         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10201       }
10202     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10203                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10204       board[EP_STATUS] = EP_NONE;
10205       board[fromY][fromX] = EmptySquare;
10206       board[toY][toX] = EmptySquare;
10207       if((toX > fromX) != (piece == BlackRook)) {
10208         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10209       } else {
10210         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10211       }
10212     /* End of code added by Tord */
10213
10214     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10215         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10216         board[toY][toX] = piece;
10217     } else if (board[fromY][fromX] == king
10218         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10219         && toY == fromY && toX > fromX+1) {
10220         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10221         board[fromY][toX-1] = board[fromY][rookX];
10222         board[fromY][rookX] = EmptySquare;
10223         board[fromY][fromX] = EmptySquare;
10224         board[toY][toX] = king;
10225     } else if (board[fromY][fromX] == king
10226         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10227                && toY == fromY && toX < fromX-1) {
10228         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10229         board[fromY][toX+1] = board[fromY][rookX];
10230         board[fromY][rookX] = EmptySquare;
10231         board[fromY][fromX] = EmptySquare;
10232         board[toY][toX] = king;
10233     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10234                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10235                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10236                ) {
10237         /* white pawn promotion */
10238         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10239         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10240             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10241         board[fromY][fromX] = EmptySquare;
10242     } else if ((fromY >= BOARD_HEIGHT>>1)
10243                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10244                && (toX != fromX)
10245                && gameInfo.variant != VariantXiangqi
10246                && gameInfo.variant != VariantBerolina
10247                && (pawn == WhitePawn)
10248                && (board[toY][toX] == EmptySquare)) {
10249         board[fromY][fromX] = EmptySquare;
10250         board[toY][toX] = piece;
10251         if(toY == epRank - 128 + 1)
10252             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10253         else
10254             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10255     } else if ((fromY == BOARD_HEIGHT-4)
10256                && (toX == fromX)
10257                && gameInfo.variant == VariantBerolina
10258                && (board[fromY][fromX] == WhitePawn)
10259                && (board[toY][toX] == EmptySquare)) {
10260         board[fromY][fromX] = EmptySquare;
10261         board[toY][toX] = WhitePawn;
10262         if(oldEP & EP_BEROLIN_A) {
10263                 captured = board[fromY][fromX-1];
10264                 board[fromY][fromX-1] = EmptySquare;
10265         }else{  captured = board[fromY][fromX+1];
10266                 board[fromY][fromX+1] = EmptySquare;
10267         }
10268     } else if (board[fromY][fromX] == king
10269         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10270                && toY == fromY && toX > fromX+1) {
10271         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10272         board[fromY][toX-1] = board[fromY][rookX];
10273         board[fromY][rookX] = EmptySquare;
10274         board[fromY][fromX] = EmptySquare;
10275         board[toY][toX] = king;
10276     } else if (board[fromY][fromX] == king
10277         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10278                && toY == fromY && toX < fromX-1) {
10279         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10280         board[fromY][toX+1] = board[fromY][rookX];
10281         board[fromY][rookX] = EmptySquare;
10282         board[fromY][fromX] = EmptySquare;
10283         board[toY][toX] = king;
10284     } else if (fromY == 7 && fromX == 3
10285                && board[fromY][fromX] == BlackKing
10286                && toY == 7 && toX == 5) {
10287         board[fromY][fromX] = EmptySquare;
10288         board[toY][toX] = BlackKing;
10289         board[fromY][7] = EmptySquare;
10290         board[toY][4] = BlackRook;
10291     } else if (fromY == 7 && fromX == 3
10292                && board[fromY][fromX] == BlackKing
10293                && toY == 7 && toX == 1) {
10294         board[fromY][fromX] = EmptySquare;
10295         board[toY][toX] = BlackKing;
10296         board[fromY][0] = EmptySquare;
10297         board[toY][2] = BlackRook;
10298     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10299                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10300                && toY < promoRank && promoChar
10301                ) {
10302         /* black pawn promotion */
10303         board[toY][toX] = CharToPiece(ToLower(promoChar));
10304         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10305             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10306         board[fromY][fromX] = EmptySquare;
10307     } else if ((fromY < BOARD_HEIGHT>>1)
10308                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10309                && (toX != fromX)
10310                && gameInfo.variant != VariantXiangqi
10311                && gameInfo.variant != VariantBerolina
10312                && (pawn == BlackPawn)
10313                && (board[toY][toX] == EmptySquare)) {
10314         board[fromY][fromX] = EmptySquare;
10315         board[toY][toX] = piece;
10316         if(toY == epRank - 128 - 1)
10317             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10318         else
10319             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10320     } else if ((fromY == 3)
10321                && (toX == fromX)
10322                && gameInfo.variant == VariantBerolina
10323                && (board[fromY][fromX] == BlackPawn)
10324                && (board[toY][toX] == EmptySquare)) {
10325         board[fromY][fromX] = EmptySquare;
10326         board[toY][toX] = BlackPawn;
10327         if(oldEP & EP_BEROLIN_A) {
10328                 captured = board[fromY][fromX-1];
10329                 board[fromY][fromX-1] = EmptySquare;
10330         }else{  captured = board[fromY][fromX+1];
10331                 board[fromY][fromX+1] = EmptySquare;
10332         }
10333     } else {
10334         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10335         board[fromY][fromX] = EmptySquare;
10336         board[toY][toX] = piece;
10337     }
10338   }
10339
10340     if (gameInfo.holdingsWidth != 0) {
10341
10342       /* !!A lot more code needs to be written to support holdings  */
10343       /* [HGM] OK, so I have written it. Holdings are stored in the */
10344       /* penultimate board files, so they are automaticlly stored   */
10345       /* in the game history.                                       */
10346       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10347                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10348         /* Delete from holdings, by decreasing count */
10349         /* and erasing image if necessary            */
10350         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10351         if(p < (int) BlackPawn) { /* white drop */
10352              p -= (int)WhitePawn;
10353                  p = PieceToNumber((ChessSquare)p);
10354              if(p >= gameInfo.holdingsSize) p = 0;
10355              if(--board[p][BOARD_WIDTH-2] <= 0)
10356                   board[p][BOARD_WIDTH-1] = EmptySquare;
10357              if((int)board[p][BOARD_WIDTH-2] < 0)
10358                         board[p][BOARD_WIDTH-2] = 0;
10359         } else {                  /* black drop */
10360              p -= (int)BlackPawn;
10361                  p = PieceToNumber((ChessSquare)p);
10362              if(p >= gameInfo.holdingsSize) p = 0;
10363              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10364                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10365              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10366                         board[BOARD_HEIGHT-1-p][1] = 0;
10367         }
10368       }
10369       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10370           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10371         /* [HGM] holdings: Add to holdings, if holdings exist */
10372         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10373                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10374                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10375         }
10376         p = (int) captured;
10377         if (p >= (int) BlackPawn) {
10378           p -= (int)BlackPawn;
10379           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10380                   /* Restore shogi-promoted piece to its original  first */
10381                   captured = (ChessSquare) (DEMOTED(captured));
10382                   p = DEMOTED(p);
10383           }
10384           p = PieceToNumber((ChessSquare)p);
10385           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10386           board[p][BOARD_WIDTH-2]++;
10387           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10388         } else {
10389           p -= (int)WhitePawn;
10390           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10391                   captured = (ChessSquare) (DEMOTED(captured));
10392                   p = DEMOTED(p);
10393           }
10394           p = PieceToNumber((ChessSquare)p);
10395           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10396           board[BOARD_HEIGHT-1-p][1]++;
10397           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10398         }
10399       }
10400     } else if (gameInfo.variant == VariantAtomic) {
10401       if (captured != EmptySquare) {
10402         int y, x;
10403         for (y = toY-1; y <= toY+1; y++) {
10404           for (x = toX-1; x <= toX+1; x++) {
10405             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10406                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10407               board[y][x] = EmptySquare;
10408             }
10409           }
10410         }
10411         board[toY][toX] = EmptySquare;
10412       }
10413     }
10414
10415     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10416         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10417     } else
10418     if(promoChar == '+') {
10419         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10420         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10421         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10422           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10423     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10424         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10425         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10426            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10427         board[toY][toX] = newPiece;
10428     }
10429     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10430                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10431         // [HGM] superchess: take promotion piece out of holdings
10432         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10433         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10434             if(!--board[k][BOARD_WIDTH-2])
10435                 board[k][BOARD_WIDTH-1] = EmptySquare;
10436         } else {
10437             if(!--board[BOARD_HEIGHT-1-k][1])
10438                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10439         }
10440     }
10441 }
10442
10443 /* Updates forwardMostMove */
10444 void
10445 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10446 {
10447     int x = toX, y = toY;
10448     char *s = parseList[forwardMostMove];
10449     ChessSquare p = boards[forwardMostMove][toY][toX];
10450 //    forwardMostMove++; // [HGM] bare: moved downstream
10451
10452     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10453     (void) CoordsToAlgebraic(boards[forwardMostMove],
10454                              PosFlags(forwardMostMove),
10455                              fromY, fromX, y, x, promoChar,
10456                              s);
10457     if(killX >= 0 && killY >= 0)
10458         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10459
10460     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10461         int timeLeft; static int lastLoadFlag=0; int king, piece;
10462         piece = boards[forwardMostMove][fromY][fromX];
10463         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10464         if(gameInfo.variant == VariantKnightmate)
10465             king += (int) WhiteUnicorn - (int) WhiteKing;
10466         if(forwardMostMove == 0) {
10467             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10468                 fprintf(serverMoves, "%s;", UserName());
10469             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10470                 fprintf(serverMoves, "%s;", second.tidy);
10471             fprintf(serverMoves, "%s;", first.tidy);
10472             if(gameMode == MachinePlaysWhite)
10473                 fprintf(serverMoves, "%s;", UserName());
10474             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10475                 fprintf(serverMoves, "%s;", second.tidy);
10476         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10477         lastLoadFlag = loadFlag;
10478         // print base move
10479         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10480         // print castling suffix
10481         if( toY == fromY && piece == king ) {
10482             if(toX-fromX > 1)
10483                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10484             if(fromX-toX >1)
10485                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10486         }
10487         // e.p. suffix
10488         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10489              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10490              boards[forwardMostMove][toY][toX] == EmptySquare
10491              && fromX != toX && fromY != toY)
10492                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10493         // promotion suffix
10494         if(promoChar != NULLCHAR) {
10495             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10496                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10497                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10498             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10499         }
10500         if(!loadFlag) {
10501                 char buf[MOVE_LEN*2], *p; int len;
10502             fprintf(serverMoves, "/%d/%d",
10503                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10504             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10505             else                      timeLeft = blackTimeRemaining/1000;
10506             fprintf(serverMoves, "/%d", timeLeft);
10507                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10508                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10509                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10510                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10511             fprintf(serverMoves, "/%s", buf);
10512         }
10513         fflush(serverMoves);
10514     }
10515
10516     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10517         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10518       return;
10519     }
10520     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10521     if (commentList[forwardMostMove+1] != NULL) {
10522         free(commentList[forwardMostMove+1]);
10523         commentList[forwardMostMove+1] = NULL;
10524     }
10525     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10526     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10527     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10528     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10529     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10530     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10531     adjustedClock = FALSE;
10532     gameInfo.result = GameUnfinished;
10533     if (gameInfo.resultDetails != NULL) {
10534         free(gameInfo.resultDetails);
10535         gameInfo.resultDetails = NULL;
10536     }
10537     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10538                               moveList[forwardMostMove - 1]);
10539     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10540       case MT_NONE:
10541       case MT_STALEMATE:
10542       default:
10543         break;
10544       case MT_CHECK:
10545         if(!IS_SHOGI(gameInfo.variant))
10546             strcat(parseList[forwardMostMove - 1], "+");
10547         break;
10548       case MT_CHECKMATE:
10549       case MT_STAINMATE:
10550         strcat(parseList[forwardMostMove - 1], "#");
10551         break;
10552     }
10553 }
10554
10555 /* Updates currentMove if not pausing */
10556 void
10557 ShowMove (int fromX, int fromY, int toX, int toY)
10558 {
10559     int instant = (gameMode == PlayFromGameFile) ?
10560         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10561     if(appData.noGUI) return;
10562     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10563         if (!instant) {
10564             if (forwardMostMove == currentMove + 1) {
10565                 AnimateMove(boards[forwardMostMove - 1],
10566                             fromX, fromY, toX, toY);
10567             }
10568         }
10569         currentMove = forwardMostMove;
10570     }
10571
10572     killX = killY = -1; // [HGM] lion: used up
10573
10574     if (instant) return;
10575
10576     DisplayMove(currentMove - 1);
10577     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10578             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10579                 SetHighlights(fromX, fromY, toX, toY);
10580             }
10581     }
10582     DrawPosition(FALSE, boards[currentMove]);
10583     DisplayBothClocks();
10584     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10585 }
10586
10587 void
10588 SendEgtPath (ChessProgramState *cps)
10589 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10590         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10591
10592         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10593
10594         while(*p) {
10595             char c, *q = name+1, *r, *s;
10596
10597             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10598             while(*p && *p != ',') *q++ = *p++;
10599             *q++ = ':'; *q = 0;
10600             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10601                 strcmp(name, ",nalimov:") == 0 ) {
10602                 // take nalimov path from the menu-changeable option first, if it is defined
10603               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10604                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10605             } else
10606             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10607                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10608                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10609                 s = r = StrStr(s, ":") + 1; // beginning of path info
10610                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10611                 c = *r; *r = 0;             // temporarily null-terminate path info
10612                     *--q = 0;               // strip of trailig ':' from name
10613                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10614                 *r = c;
10615                 SendToProgram(buf,cps);     // send egtbpath command for this format
10616             }
10617             if(*p == ',') p++; // read away comma to position for next format name
10618         }
10619 }
10620
10621 static int
10622 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10623 {
10624       int width = 8, height = 8, holdings = 0;             // most common sizes
10625       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10626       // correct the deviations default for each variant
10627       if( v == VariantXiangqi ) width = 9,  height = 10;
10628       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10629       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10630       if( v == VariantCapablanca || v == VariantCapaRandom ||
10631           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10632                                 width = 10;
10633       if( v == VariantCourier ) width = 12;
10634       if( v == VariantSuper )                            holdings = 8;
10635       if( v == VariantGreat )   width = 10,              holdings = 8;
10636       if( v == VariantSChess )                           holdings = 7;
10637       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10638       if( v == VariantChuChess) width = 10, height = 10;
10639       if( v == VariantChu )     width = 12, height = 12;
10640       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10641              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10642              holdingsSize >= 0 && holdingsSize != holdings;
10643 }
10644
10645 char variantError[MSG_SIZ];
10646
10647 char *
10648 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10649 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10650       char *p, *variant = VariantName(v);
10651       static char b[MSG_SIZ];
10652       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10653            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10654                                                holdingsSize, variant); // cook up sized variant name
10655            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10656            if(StrStr(list, b) == NULL) {
10657                // specific sized variant not known, check if general sizing allowed
10658                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10659                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10660                             boardWidth, boardHeight, holdingsSize, engine);
10661                    return NULL;
10662                }
10663                /* [HGM] here we really should compare with the maximum supported board size */
10664            }
10665       } else snprintf(b, MSG_SIZ,"%s", variant);
10666       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10667       p = StrStr(list, b);
10668       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10669       if(p == NULL) {
10670           // occurs not at all in list, or only as sub-string
10671           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10672           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10673               int l = strlen(variantError);
10674               char *q;
10675               while(p != list && p[-1] != ',') p--;
10676               q = strchr(p, ',');
10677               if(q) *q = NULLCHAR;
10678               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10679               if(q) *q= ',';
10680           }
10681           return NULL;
10682       }
10683       return b;
10684 }
10685
10686 void
10687 InitChessProgram (ChessProgramState *cps, int setup)
10688 /* setup needed to setup FRC opening position */
10689 {
10690     char buf[MSG_SIZ], *b;
10691     if (appData.noChessProgram) return;
10692     hintRequested = FALSE;
10693     bookRequested = FALSE;
10694
10695     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10696     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10697     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10698     if(cps->memSize) { /* [HGM] memory */
10699       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10700         SendToProgram(buf, cps);
10701     }
10702     SendEgtPath(cps); /* [HGM] EGT */
10703     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10704       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10705         SendToProgram(buf, cps);
10706     }
10707
10708     setboardSpoiledMachineBlack = FALSE;
10709     SendToProgram(cps->initString, cps);
10710     if (gameInfo.variant != VariantNormal &&
10711         gameInfo.variant != VariantLoadable
10712         /* [HGM] also send variant if board size non-standard */
10713         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10714
10715       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10716                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10717       if (b == NULL) {
10718         VariantClass v;
10719         char c, *q = cps->variants, *p = strchr(q, ',');
10720         if(p) *p = NULLCHAR;
10721         v = StringToVariant(q);
10722         DisplayError(variantError, 0);
10723         if(v != VariantUnknown && cps == &first) {
10724             int w, h, s;
10725             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10726                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10727             ASSIGN(appData.variant, q);
10728             Reset(TRUE, FALSE);
10729         }
10730         if(p) *p = ',';
10731         return;
10732       }
10733
10734       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10735       SendToProgram(buf, cps);
10736     }
10737     currentlyInitializedVariant = gameInfo.variant;
10738
10739     /* [HGM] send opening position in FRC to first engine */
10740     if(setup) {
10741           SendToProgram("force\n", cps);
10742           SendBoard(cps, 0);
10743           /* engine is now in force mode! Set flag to wake it up after first move. */
10744           setboardSpoiledMachineBlack = 1;
10745     }
10746
10747     if (cps->sendICS) {
10748       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10749       SendToProgram(buf, cps);
10750     }
10751     cps->maybeThinking = FALSE;
10752     cps->offeredDraw = 0;
10753     if (!appData.icsActive) {
10754         SendTimeControl(cps, movesPerSession, timeControl,
10755                         timeIncrement, appData.searchDepth,
10756                         searchTime);
10757     }
10758     if (appData.showThinking
10759         // [HGM] thinking: four options require thinking output to be sent
10760         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10761                                 ) {
10762         SendToProgram("post\n", cps);
10763     }
10764     SendToProgram("hard\n", cps);
10765     if (!appData.ponderNextMove) {
10766         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10767            it without being sure what state we are in first.  "hard"
10768            is not a toggle, so that one is OK.
10769          */
10770         SendToProgram("easy\n", cps);
10771     }
10772     if (cps->usePing) {
10773       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10774       SendToProgram(buf, cps);
10775     }
10776     cps->initDone = TRUE;
10777     ClearEngineOutputPane(cps == &second);
10778 }
10779
10780
10781 void
10782 ResendOptions (ChessProgramState *cps)
10783 { // send the stored value of the options
10784   int i;
10785   char buf[MSG_SIZ];
10786   Option *opt = cps->option;
10787   for(i=0; i<cps->nrOptions; i++, opt++) {
10788       switch(opt->type) {
10789         case Spin:
10790         case Slider:
10791         case CheckBox:
10792             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10793           break;
10794         case ComboBox:
10795           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10796           break;
10797         default:
10798             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10799           break;
10800         case Button:
10801         case SaveButton:
10802           continue;
10803       }
10804       SendToProgram(buf, cps);
10805   }
10806 }
10807
10808 void
10809 StartChessProgram (ChessProgramState *cps)
10810 {
10811     char buf[MSG_SIZ];
10812     int err;
10813
10814     if (appData.noChessProgram) return;
10815     cps->initDone = FALSE;
10816
10817     if (strcmp(cps->host, "localhost") == 0) {
10818         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10819     } else if (*appData.remoteShell == NULLCHAR) {
10820         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10821     } else {
10822         if (*appData.remoteUser == NULLCHAR) {
10823           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10824                     cps->program);
10825         } else {
10826           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10827                     cps->host, appData.remoteUser, cps->program);
10828         }
10829         err = StartChildProcess(buf, "", &cps->pr);
10830     }
10831
10832     if (err != 0) {
10833       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10834         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10835         if(cps != &first) return;
10836         appData.noChessProgram = TRUE;
10837         ThawUI();
10838         SetNCPMode();
10839 //      DisplayFatalError(buf, err, 1);
10840 //      cps->pr = NoProc;
10841 //      cps->isr = NULL;
10842         return;
10843     }
10844
10845     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10846     if (cps->protocolVersion > 1) {
10847       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10848       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10849         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10850         cps->comboCnt = 0;  //                and values of combo boxes
10851       }
10852       SendToProgram(buf, cps);
10853       if(cps->reload) ResendOptions(cps);
10854     } else {
10855       SendToProgram("xboard\n", cps);
10856     }
10857 }
10858
10859 void
10860 TwoMachinesEventIfReady P((void))
10861 {
10862   static int curMess = 0;
10863   if (first.lastPing != first.lastPong) {
10864     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10865     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10866     return;
10867   }
10868   if (second.lastPing != second.lastPong) {
10869     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10870     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10871     return;
10872   }
10873   DisplayMessage("", ""); curMess = 0;
10874   TwoMachinesEvent();
10875 }
10876
10877 char *
10878 MakeName (char *template)
10879 {
10880     time_t clock;
10881     struct tm *tm;
10882     static char buf[MSG_SIZ];
10883     char *p = buf;
10884     int i;
10885
10886     clock = time((time_t *)NULL);
10887     tm = localtime(&clock);
10888
10889     while(*p++ = *template++) if(p[-1] == '%') {
10890         switch(*template++) {
10891           case 0:   *p = 0; return buf;
10892           case 'Y': i = tm->tm_year+1900; break;
10893           case 'y': i = tm->tm_year-100; break;
10894           case 'M': i = tm->tm_mon+1; break;
10895           case 'd': i = tm->tm_mday; break;
10896           case 'h': i = tm->tm_hour; break;
10897           case 'm': i = tm->tm_min; break;
10898           case 's': i = tm->tm_sec; break;
10899           default:  i = 0;
10900         }
10901         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10902     }
10903     return buf;
10904 }
10905
10906 int
10907 CountPlayers (char *p)
10908 {
10909     int n = 0;
10910     while(p = strchr(p, '\n')) p++, n++; // count participants
10911     return n;
10912 }
10913
10914 FILE *
10915 WriteTourneyFile (char *results, FILE *f)
10916 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10917     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10918     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10919         // create a file with tournament description
10920         fprintf(f, "-participants {%s}\n", appData.participants);
10921         fprintf(f, "-seedBase %d\n", appData.seedBase);
10922         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10923         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10924         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10925         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10926         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10927         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10928         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10929         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10930         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10931         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10932         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10933         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10934         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10935         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10936         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10937         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10938         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10939         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10940         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10941         fprintf(f, "-smpCores %d\n", appData.smpCores);
10942         if(searchTime > 0)
10943                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10944         else {
10945                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10946                 fprintf(f, "-tc %s\n", appData.timeControl);
10947                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10948         }
10949         fprintf(f, "-results \"%s\"\n", results);
10950     }
10951     return f;
10952 }
10953
10954 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10955
10956 void
10957 Substitute (char *participants, int expunge)
10958 {
10959     int i, changed, changes=0, nPlayers=0;
10960     char *p, *q, *r, buf[MSG_SIZ];
10961     if(participants == NULL) return;
10962     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10963     r = p = participants; q = appData.participants;
10964     while(*p && *p == *q) {
10965         if(*p == '\n') r = p+1, nPlayers++;
10966         p++; q++;
10967     }
10968     if(*p) { // difference
10969         while(*p && *p++ != '\n');
10970         while(*q && *q++ != '\n');
10971       changed = nPlayers;
10972         changes = 1 + (strcmp(p, q) != 0);
10973     }
10974     if(changes == 1) { // a single engine mnemonic was changed
10975         q = r; while(*q) nPlayers += (*q++ == '\n');
10976         p = buf; while(*r && (*p = *r++) != '\n') p++;
10977         *p = NULLCHAR;
10978         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10979         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10980         if(mnemonic[i]) { // The substitute is valid
10981             FILE *f;
10982             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10983                 flock(fileno(f), LOCK_EX);
10984                 ParseArgsFromFile(f);
10985                 fseek(f, 0, SEEK_SET);
10986                 FREE(appData.participants); appData.participants = participants;
10987                 if(expunge) { // erase results of replaced engine
10988                     int len = strlen(appData.results), w, b, dummy;
10989                     for(i=0; i<len; i++) {
10990                         Pairing(i, nPlayers, &w, &b, &dummy);
10991                         if((w == changed || b == changed) && appData.results[i] == '*') {
10992                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10993                             fclose(f);
10994                             return;
10995                         }
10996                     }
10997                     for(i=0; i<len; i++) {
10998                         Pairing(i, nPlayers, &w, &b, &dummy);
10999                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11000                     }
11001                 }
11002                 WriteTourneyFile(appData.results, f);
11003                 fclose(f); // release lock
11004                 return;
11005             }
11006         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11007     }
11008     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11009     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11010     free(participants);
11011     return;
11012 }
11013
11014 int
11015 CheckPlayers (char *participants)
11016 {
11017         int i;
11018         char buf[MSG_SIZ], *p;
11019         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11020         while(p = strchr(participants, '\n')) {
11021             *p = NULLCHAR;
11022             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11023             if(!mnemonic[i]) {
11024                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11025                 *p = '\n';
11026                 DisplayError(buf, 0);
11027                 return 1;
11028             }
11029             *p = '\n';
11030             participants = p + 1;
11031         }
11032         return 0;
11033 }
11034
11035 int
11036 CreateTourney (char *name)
11037 {
11038         FILE *f;
11039         if(matchMode && strcmp(name, appData.tourneyFile)) {
11040              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11041         }
11042         if(name[0] == NULLCHAR) {
11043             if(appData.participants[0])
11044                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11045             return 0;
11046         }
11047         f = fopen(name, "r");
11048         if(f) { // file exists
11049             ASSIGN(appData.tourneyFile, name);
11050             ParseArgsFromFile(f); // parse it
11051         } else {
11052             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11053             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11054                 DisplayError(_("Not enough participants"), 0);
11055                 return 0;
11056             }
11057             if(CheckPlayers(appData.participants)) return 0;
11058             ASSIGN(appData.tourneyFile, name);
11059             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11060             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11061         }
11062         fclose(f);
11063         appData.noChessProgram = FALSE;
11064         appData.clockMode = TRUE;
11065         SetGNUMode();
11066         return 1;
11067 }
11068
11069 int
11070 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11071 {
11072     char buf[MSG_SIZ], *p, *q;
11073     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11074     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11075     skip = !all && group[0]; // if group requested, we start in skip mode
11076     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11077         p = names; q = buf; header = 0;
11078         while(*p && *p != '\n') *q++ = *p++;
11079         *q = 0;
11080         if(*p == '\n') p++;
11081         if(buf[0] == '#') {
11082             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11083             depth++; // we must be entering a new group
11084             if(all) continue; // suppress printing group headers when complete list requested
11085             header = 1;
11086             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11087         }
11088         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11089         if(engineList[i]) free(engineList[i]);
11090         engineList[i] = strdup(buf);
11091         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11092         if(engineMnemonic[i]) free(engineMnemonic[i]);
11093         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11094             strcat(buf, " (");
11095             sscanf(q + 8, "%s", buf + strlen(buf));
11096             strcat(buf, ")");
11097         }
11098         engineMnemonic[i] = strdup(buf);
11099         i++;
11100     }
11101     engineList[i] = engineMnemonic[i] = NULL;
11102     return i;
11103 }
11104
11105 // following implemented as macro to avoid type limitations
11106 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11107
11108 void
11109 SwapEngines (int n)
11110 {   // swap settings for first engine and other engine (so far only some selected options)
11111     int h;
11112     char *p;
11113     if(n == 0) return;
11114     SWAP(directory, p)
11115     SWAP(chessProgram, p)
11116     SWAP(isUCI, h)
11117     SWAP(hasOwnBookUCI, h)
11118     SWAP(protocolVersion, h)
11119     SWAP(reuse, h)
11120     SWAP(scoreIsAbsolute, h)
11121     SWAP(timeOdds, h)
11122     SWAP(logo, p)
11123     SWAP(pgnName, p)
11124     SWAP(pvSAN, h)
11125     SWAP(engOptions, p)
11126     SWAP(engInitString, p)
11127     SWAP(computerString, p)
11128     SWAP(features, p)
11129     SWAP(fenOverride, p)
11130     SWAP(NPS, h)
11131     SWAP(accumulateTC, h)
11132     SWAP(drawDepth, h)
11133     SWAP(host, p)
11134     SWAP(pseudo, h)
11135 }
11136
11137 int
11138 GetEngineLine (char *s, int n)
11139 {
11140     int i;
11141     char buf[MSG_SIZ];
11142     extern char *icsNames;
11143     if(!s || !*s) return 0;
11144     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11145     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11146     if(!mnemonic[i]) return 0;
11147     if(n == 11) return 1; // just testing if there was a match
11148     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11149     if(n == 1) SwapEngines(n);
11150     ParseArgsFromString(buf);
11151     if(n == 1) SwapEngines(n);
11152     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11153         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11154         ParseArgsFromString(buf);
11155     }
11156     return 1;
11157 }
11158
11159 int
11160 SetPlayer (int player, char *p)
11161 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11162     int i;
11163     char buf[MSG_SIZ], *engineName;
11164     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11165     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11166     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11167     if(mnemonic[i]) {
11168         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11169         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11170         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11171         ParseArgsFromString(buf);
11172     } else { // no engine with this nickname is installed!
11173         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11174         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11175         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11176         ModeHighlight();
11177         DisplayError(buf, 0);
11178         return 0;
11179     }
11180     free(engineName);
11181     return i;
11182 }
11183
11184 char *recentEngines;
11185
11186 void
11187 RecentEngineEvent (int nr)
11188 {
11189     int n;
11190 //    SwapEngines(1); // bump first to second
11191 //    ReplaceEngine(&second, 1); // and load it there
11192     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11193     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11194     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11195         ReplaceEngine(&first, 0);
11196         FloatToFront(&appData.recentEngineList, command[n]);
11197     }
11198 }
11199
11200 int
11201 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11202 {   // determine players from game number
11203     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11204
11205     if(appData.tourneyType == 0) {
11206         roundsPerCycle = (nPlayers - 1) | 1;
11207         pairingsPerRound = nPlayers / 2;
11208     } else if(appData.tourneyType > 0) {
11209         roundsPerCycle = nPlayers - appData.tourneyType;
11210         pairingsPerRound = appData.tourneyType;
11211     }
11212     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11213     gamesPerCycle = gamesPerRound * roundsPerCycle;
11214     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11215     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11216     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11217     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11218     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11219     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11220
11221     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11222     if(appData.roundSync) *syncInterval = gamesPerRound;
11223
11224     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11225
11226     if(appData.tourneyType == 0) {
11227         if(curPairing == (nPlayers-1)/2 ) {
11228             *whitePlayer = curRound;
11229             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11230         } else {
11231             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11232             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11233             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11234             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11235         }
11236     } else if(appData.tourneyType > 1) {
11237         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11238         *whitePlayer = curRound + appData.tourneyType;
11239     } else if(appData.tourneyType > 0) {
11240         *whitePlayer = curPairing;
11241         *blackPlayer = curRound + appData.tourneyType;
11242     }
11243
11244     // take care of white/black alternation per round.
11245     // For cycles and games this is already taken care of by default, derived from matchGame!
11246     return curRound & 1;
11247 }
11248
11249 int
11250 NextTourneyGame (int nr, int *swapColors)
11251 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11252     char *p, *q;
11253     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11254     FILE *tf;
11255     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11256     tf = fopen(appData.tourneyFile, "r");
11257     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11258     ParseArgsFromFile(tf); fclose(tf);
11259     InitTimeControls(); // TC might be altered from tourney file
11260
11261     nPlayers = CountPlayers(appData.participants); // count participants
11262     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11263     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11264
11265     if(syncInterval) {
11266         p = q = appData.results;
11267         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11268         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11269             DisplayMessage(_("Waiting for other game(s)"),"");
11270             waitingForGame = TRUE;
11271             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11272             return 0;
11273         }
11274         waitingForGame = FALSE;
11275     }
11276
11277     if(appData.tourneyType < 0) {
11278         if(nr>=0 && !pairingReceived) {
11279             char buf[1<<16];
11280             if(pairing.pr == NoProc) {
11281                 if(!appData.pairingEngine[0]) {
11282                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11283                     return 0;
11284                 }
11285                 StartChessProgram(&pairing); // starts the pairing engine
11286             }
11287             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11288             SendToProgram(buf, &pairing);
11289             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11290             SendToProgram(buf, &pairing);
11291             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11292         }
11293         pairingReceived = 0;                              // ... so we continue here
11294         *swapColors = 0;
11295         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11296         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11297         matchGame = 1; roundNr = nr / syncInterval + 1;
11298     }
11299
11300     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11301
11302     // redefine engines, engine dir, etc.
11303     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11304     if(first.pr == NoProc) {
11305       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11306       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11307     }
11308     if(second.pr == NoProc) {
11309       SwapEngines(1);
11310       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11311       SwapEngines(1);         // and make that valid for second engine by swapping
11312       InitEngine(&second, 1);
11313     }
11314     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11315     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11316     return OK;
11317 }
11318
11319 void
11320 NextMatchGame ()
11321 {   // performs game initialization that does not invoke engines, and then tries to start the game
11322     int res, firstWhite, swapColors = 0;
11323     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11324     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
11325         char buf[MSG_SIZ];
11326         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11327         if(strcmp(buf, currentDebugFile)) { // name has changed
11328             FILE *f = fopen(buf, "w");
11329             if(f) { // if opening the new file failed, just keep using the old one
11330                 ASSIGN(currentDebugFile, buf);
11331                 fclose(debugFP);
11332                 debugFP = f;
11333             }
11334             if(appData.serverFileName) {
11335                 if(serverFP) fclose(serverFP);
11336                 serverFP = fopen(appData.serverFileName, "w");
11337                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11338                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11339             }
11340         }
11341     }
11342     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11343     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11344     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11345     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11346     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11347     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11348     Reset(FALSE, first.pr != NoProc);
11349     res = LoadGameOrPosition(matchGame); // setup game
11350     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11351     if(!res) return; // abort when bad game/pos file
11352     TwoMachinesEvent();
11353 }
11354
11355 void
11356 UserAdjudicationEvent (int result)
11357 {
11358     ChessMove gameResult = GameIsDrawn;
11359
11360     if( result > 0 ) {
11361         gameResult = WhiteWins;
11362     }
11363     else if( result < 0 ) {
11364         gameResult = BlackWins;
11365     }
11366
11367     if( gameMode == TwoMachinesPlay ) {
11368         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11369     }
11370 }
11371
11372
11373 // [HGM] save: calculate checksum of game to make games easily identifiable
11374 int
11375 StringCheckSum (char *s)
11376 {
11377         int i = 0;
11378         if(s==NULL) return 0;
11379         while(*s) i = i*259 + *s++;
11380         return i;
11381 }
11382
11383 int
11384 GameCheckSum ()
11385 {
11386         int i, sum=0;
11387         for(i=backwardMostMove; i<forwardMostMove; i++) {
11388                 sum += pvInfoList[i].depth;
11389                 sum += StringCheckSum(parseList[i]);
11390                 sum += StringCheckSum(commentList[i]);
11391                 sum *= 261;
11392         }
11393         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11394         return sum + StringCheckSum(commentList[i]);
11395 } // end of save patch
11396
11397 void
11398 GameEnds (ChessMove result, char *resultDetails, int whosays)
11399 {
11400     GameMode nextGameMode;
11401     int isIcsGame;
11402     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11403
11404     if(endingGame) return; /* [HGM] crash: forbid recursion */
11405     endingGame = 1;
11406     if(twoBoards) { // [HGM] dual: switch back to one board
11407         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11408         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11409     }
11410     if (appData.debugMode) {
11411       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11412               result, resultDetails ? resultDetails : "(null)", whosays);
11413     }
11414
11415     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11416
11417     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11418
11419     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11420         /* If we are playing on ICS, the server decides when the
11421            game is over, but the engine can offer to draw, claim
11422            a draw, or resign.
11423          */
11424 #if ZIPPY
11425         if (appData.zippyPlay && first.initDone) {
11426             if (result == GameIsDrawn) {
11427                 /* In case draw still needs to be claimed */
11428                 SendToICS(ics_prefix);
11429                 SendToICS("draw\n");
11430             } else if (StrCaseStr(resultDetails, "resign")) {
11431                 SendToICS(ics_prefix);
11432                 SendToICS("resign\n");
11433             }
11434         }
11435 #endif
11436         endingGame = 0; /* [HGM] crash */
11437         return;
11438     }
11439
11440     /* If we're loading the game from a file, stop */
11441     if (whosays == GE_FILE) {
11442       (void) StopLoadGameTimer();
11443       gameFileFP = NULL;
11444     }
11445
11446     /* Cancel draw offers */
11447     first.offeredDraw = second.offeredDraw = 0;
11448
11449     /* If this is an ICS game, only ICS can really say it's done;
11450        if not, anyone can. */
11451     isIcsGame = (gameMode == IcsPlayingWhite ||
11452                  gameMode == IcsPlayingBlack ||
11453                  gameMode == IcsObserving    ||
11454                  gameMode == IcsExamining);
11455
11456     if (!isIcsGame || whosays == GE_ICS) {
11457         /* OK -- not an ICS game, or ICS said it was done */
11458         StopClocks();
11459         if (!isIcsGame && !appData.noChessProgram)
11460           SetUserThinkingEnables();
11461
11462         /* [HGM] if a machine claims the game end we verify this claim */
11463         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11464             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11465                 char claimer;
11466                 ChessMove trueResult = (ChessMove) -1;
11467
11468                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11469                                             first.twoMachinesColor[0] :
11470                                             second.twoMachinesColor[0] ;
11471
11472                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11473                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11474                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11475                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11476                 } else
11477                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11478                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11479                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11480                 } else
11481                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11482                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11483                 }
11484
11485                 // now verify win claims, but not in drop games, as we don't understand those yet
11486                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11487                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11488                     (result == WhiteWins && claimer == 'w' ||
11489                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11490                       if (appData.debugMode) {
11491                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11492                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11493                       }
11494                       if(result != trueResult) {
11495                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11496                               result = claimer == 'w' ? BlackWins : WhiteWins;
11497                               resultDetails = buf;
11498                       }
11499                 } else
11500                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11501                     && (forwardMostMove <= backwardMostMove ||
11502                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11503                         (claimer=='b')==(forwardMostMove&1))
11504                                                                                   ) {
11505                       /* [HGM] verify: draws that were not flagged are false claims */
11506                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11507                       result = claimer == 'w' ? BlackWins : WhiteWins;
11508                       resultDetails = buf;
11509                 }
11510                 /* (Claiming a loss is accepted no questions asked!) */
11511             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11512                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11513                 result = GameUnfinished;
11514                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11515             }
11516             /* [HGM] bare: don't allow bare King to win */
11517             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11518                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11519                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11520                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11521                && result != GameIsDrawn)
11522             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11523                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11524                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11525                         if(p >= 0 && p <= (int)WhiteKing) k++;
11526                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11527                 }
11528                 if (appData.debugMode) {
11529                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11530                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11531                 }
11532                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11533                         result = GameIsDrawn;
11534                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11535                         resultDetails = buf;
11536                 }
11537             }
11538         }
11539
11540
11541         if(serverMoves != NULL && !loadFlag) { char c = '=';
11542             if(result==WhiteWins) c = '+';
11543             if(result==BlackWins) c = '-';
11544             if(resultDetails != NULL)
11545                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11546         }
11547         if (resultDetails != NULL) {
11548             gameInfo.result = result;
11549             gameInfo.resultDetails = StrSave(resultDetails);
11550
11551             /* display last move only if game was not loaded from file */
11552             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11553                 DisplayMove(currentMove - 1);
11554
11555             if (forwardMostMove != 0) {
11556                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11557                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11558                                                                 ) {
11559                     if (*appData.saveGameFile != NULLCHAR) {
11560                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11561                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11562                         else
11563                         SaveGameToFile(appData.saveGameFile, TRUE);
11564                     } else if (appData.autoSaveGames) {
11565                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11566                     }
11567                     if (*appData.savePositionFile != NULLCHAR) {
11568                         SavePositionToFile(appData.savePositionFile);
11569                     }
11570                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11571                 }
11572             }
11573
11574             /* Tell program how game ended in case it is learning */
11575             /* [HGM] Moved this to after saving the PGN, just in case */
11576             /* engine died and we got here through time loss. In that */
11577             /* case we will get a fatal error writing the pipe, which */
11578             /* would otherwise lose us the PGN.                       */
11579             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11580             /* output during GameEnds should never be fatal anymore   */
11581             if (gameMode == MachinePlaysWhite ||
11582                 gameMode == MachinePlaysBlack ||
11583                 gameMode == TwoMachinesPlay ||
11584                 gameMode == IcsPlayingWhite ||
11585                 gameMode == IcsPlayingBlack ||
11586                 gameMode == BeginningOfGame) {
11587                 char buf[MSG_SIZ];
11588                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11589                         resultDetails);
11590                 if (first.pr != NoProc) {
11591                     SendToProgram(buf, &first);
11592                 }
11593                 if (second.pr != NoProc &&
11594                     gameMode == TwoMachinesPlay) {
11595                     SendToProgram(buf, &second);
11596                 }
11597             }
11598         }
11599
11600         if (appData.icsActive) {
11601             if (appData.quietPlay &&
11602                 (gameMode == IcsPlayingWhite ||
11603                  gameMode == IcsPlayingBlack)) {
11604                 SendToICS(ics_prefix);
11605                 SendToICS("set shout 1\n");
11606             }
11607             nextGameMode = IcsIdle;
11608             ics_user_moved = FALSE;
11609             /* clean up premove.  It's ugly when the game has ended and the
11610              * premove highlights are still on the board.
11611              */
11612             if (gotPremove) {
11613               gotPremove = FALSE;
11614               ClearPremoveHighlights();
11615               DrawPosition(FALSE, boards[currentMove]);
11616             }
11617             if (whosays == GE_ICS) {
11618                 switch (result) {
11619                 case WhiteWins:
11620                     if (gameMode == IcsPlayingWhite)
11621                         PlayIcsWinSound();
11622                     else if(gameMode == IcsPlayingBlack)
11623                         PlayIcsLossSound();
11624                     break;
11625                 case BlackWins:
11626                     if (gameMode == IcsPlayingBlack)
11627                         PlayIcsWinSound();
11628                     else if(gameMode == IcsPlayingWhite)
11629                         PlayIcsLossSound();
11630                     break;
11631                 case GameIsDrawn:
11632                     PlayIcsDrawSound();
11633                     break;
11634                 default:
11635                     PlayIcsUnfinishedSound();
11636                 }
11637             }
11638             if(appData.quitNext) { ExitEvent(0); return; }
11639         } else if (gameMode == EditGame ||
11640                    gameMode == PlayFromGameFile ||
11641                    gameMode == AnalyzeMode ||
11642                    gameMode == AnalyzeFile) {
11643             nextGameMode = gameMode;
11644         } else {
11645             nextGameMode = EndOfGame;
11646         }
11647         pausing = FALSE;
11648         ModeHighlight();
11649     } else {
11650         nextGameMode = gameMode;
11651     }
11652
11653     if (appData.noChessProgram) {
11654         gameMode = nextGameMode;
11655         ModeHighlight();
11656         endingGame = 0; /* [HGM] crash */
11657         return;
11658     }
11659
11660     if (first.reuse) {
11661         /* Put first chess program into idle state */
11662         if (first.pr != NoProc &&
11663             (gameMode == MachinePlaysWhite ||
11664              gameMode == MachinePlaysBlack ||
11665              gameMode == TwoMachinesPlay ||
11666              gameMode == IcsPlayingWhite ||
11667              gameMode == IcsPlayingBlack ||
11668              gameMode == BeginningOfGame)) {
11669             SendToProgram("force\n", &first);
11670             if (first.usePing) {
11671               char buf[MSG_SIZ];
11672               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11673               SendToProgram(buf, &first);
11674             }
11675         }
11676     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11677         /* Kill off first chess program */
11678         if (first.isr != NULL)
11679           RemoveInputSource(first.isr);
11680         first.isr = NULL;
11681
11682         if (first.pr != NoProc) {
11683             ExitAnalyzeMode();
11684             DoSleep( appData.delayBeforeQuit );
11685             SendToProgram("quit\n", &first);
11686             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11687             first.reload = TRUE;
11688         }
11689         first.pr = NoProc;
11690     }
11691     if (second.reuse) {
11692         /* Put second chess program into idle state */
11693         if (second.pr != NoProc &&
11694             gameMode == TwoMachinesPlay) {
11695             SendToProgram("force\n", &second);
11696             if (second.usePing) {
11697               char buf[MSG_SIZ];
11698               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11699               SendToProgram(buf, &second);
11700             }
11701         }
11702     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11703         /* Kill off second chess program */
11704         if (second.isr != NULL)
11705           RemoveInputSource(second.isr);
11706         second.isr = NULL;
11707
11708         if (second.pr != NoProc) {
11709             DoSleep( appData.delayBeforeQuit );
11710             SendToProgram("quit\n", &second);
11711             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11712             second.reload = TRUE;
11713         }
11714         second.pr = NoProc;
11715     }
11716
11717     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11718         char resChar = '=';
11719         switch (result) {
11720         case WhiteWins:
11721           resChar = '+';
11722           if (first.twoMachinesColor[0] == 'w') {
11723             first.matchWins++;
11724           } else {
11725             second.matchWins++;
11726           }
11727           break;
11728         case BlackWins:
11729           resChar = '-';
11730           if (first.twoMachinesColor[0] == 'b') {
11731             first.matchWins++;
11732           } else {
11733             second.matchWins++;
11734           }
11735           break;
11736         case GameUnfinished:
11737           resChar = ' ';
11738         default:
11739           break;
11740         }
11741
11742         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11743         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11744             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11745             ReserveGame(nextGame, resChar); // sets nextGame
11746             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11747             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11748         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11749
11750         if (nextGame <= appData.matchGames && !abortMatch) {
11751             gameMode = nextGameMode;
11752             matchGame = nextGame; // this will be overruled in tourney mode!
11753             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11754             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11755             endingGame = 0; /* [HGM] crash */
11756             return;
11757         } else {
11758             gameMode = nextGameMode;
11759             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11760                      first.tidy, second.tidy,
11761                      first.matchWins, second.matchWins,
11762                      appData.matchGames - (first.matchWins + second.matchWins));
11763             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11764             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11765             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11766             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11767                 first.twoMachinesColor = "black\n";
11768                 second.twoMachinesColor = "white\n";
11769             } else {
11770                 first.twoMachinesColor = "white\n";
11771                 second.twoMachinesColor = "black\n";
11772             }
11773         }
11774     }
11775     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11776         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11777       ExitAnalyzeMode();
11778     gameMode = nextGameMode;
11779     ModeHighlight();
11780     endingGame = 0;  /* [HGM] crash */
11781     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11782         if(matchMode == TRUE) { // match through command line: exit with or without popup
11783             if(ranking) {
11784                 ToNrEvent(forwardMostMove);
11785                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11786                 else ExitEvent(0);
11787             } else DisplayFatalError(buf, 0, 0);
11788         } else { // match through menu; just stop, with or without popup
11789             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11790             ModeHighlight();
11791             if(ranking){
11792                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11793             } else DisplayNote(buf);
11794       }
11795       if(ranking) free(ranking);
11796     }
11797 }
11798
11799 /* Assumes program was just initialized (initString sent).
11800    Leaves program in force mode. */
11801 void
11802 FeedMovesToProgram (ChessProgramState *cps, int upto)
11803 {
11804     int i;
11805
11806     if (appData.debugMode)
11807       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11808               startedFromSetupPosition ? "position and " : "",
11809               backwardMostMove, upto, cps->which);
11810     if(currentlyInitializedVariant != gameInfo.variant) {
11811       char buf[MSG_SIZ];
11812         // [HGM] variantswitch: make engine aware of new variant
11813         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11814                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11815                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11816         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11817         SendToProgram(buf, cps);
11818         currentlyInitializedVariant = gameInfo.variant;
11819     }
11820     SendToProgram("force\n", cps);
11821     if (startedFromSetupPosition) {
11822         SendBoard(cps, backwardMostMove);
11823     if (appData.debugMode) {
11824         fprintf(debugFP, "feedMoves\n");
11825     }
11826     }
11827     for (i = backwardMostMove; i < upto; i++) {
11828         SendMoveToProgram(i, cps);
11829     }
11830 }
11831
11832
11833 int
11834 ResurrectChessProgram ()
11835 {
11836      /* The chess program may have exited.
11837         If so, restart it and feed it all the moves made so far. */
11838     static int doInit = 0;
11839
11840     if (appData.noChessProgram) return 1;
11841
11842     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11843         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11844         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11845         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11846     } else {
11847         if (first.pr != NoProc) return 1;
11848         StartChessProgram(&first);
11849     }
11850     InitChessProgram(&first, FALSE);
11851     FeedMovesToProgram(&first, currentMove);
11852
11853     if (!first.sendTime) {
11854         /* can't tell gnuchess what its clock should read,
11855            so we bow to its notion. */
11856         ResetClocks();
11857         timeRemaining[0][currentMove] = whiteTimeRemaining;
11858         timeRemaining[1][currentMove] = blackTimeRemaining;
11859     }
11860
11861     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11862                 appData.icsEngineAnalyze) && first.analysisSupport) {
11863       SendToProgram("analyze\n", &first);
11864       first.analyzing = TRUE;
11865     }
11866     return 1;
11867 }
11868
11869 /*
11870  * Button procedures
11871  */
11872 void
11873 Reset (int redraw, int init)
11874 {
11875     int i;
11876
11877     if (appData.debugMode) {
11878         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11879                 redraw, init, gameMode);
11880     }
11881     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11882     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11883     CleanupTail(); // [HGM] vari: delete any stored variations
11884     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11885     pausing = pauseExamInvalid = FALSE;
11886     startedFromSetupPosition = blackPlaysFirst = FALSE;
11887     firstMove = TRUE;
11888     whiteFlag = blackFlag = FALSE;
11889     userOfferedDraw = FALSE;
11890     hintRequested = bookRequested = FALSE;
11891     first.maybeThinking = FALSE;
11892     second.maybeThinking = FALSE;
11893     first.bookSuspend = FALSE; // [HGM] book
11894     second.bookSuspend = FALSE;
11895     thinkOutput[0] = NULLCHAR;
11896     lastHint[0] = NULLCHAR;
11897     ClearGameInfo(&gameInfo);
11898     gameInfo.variant = StringToVariant(appData.variant);
11899     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11900     ics_user_moved = ics_clock_paused = FALSE;
11901     ics_getting_history = H_FALSE;
11902     ics_gamenum = -1;
11903     white_holding[0] = black_holding[0] = NULLCHAR;
11904     ClearProgramStats();
11905     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11906
11907     ResetFrontEnd();
11908     ClearHighlights();
11909     flipView = appData.flipView;
11910     ClearPremoveHighlights();
11911     gotPremove = FALSE;
11912     alarmSounded = FALSE;
11913     killX = killY = -1; // [HGM] lion
11914
11915     GameEnds(EndOfFile, NULL, GE_PLAYER);
11916     if(appData.serverMovesName != NULL) {
11917         /* [HGM] prepare to make moves file for broadcasting */
11918         clock_t t = clock();
11919         if(serverMoves != NULL) fclose(serverMoves);
11920         serverMoves = fopen(appData.serverMovesName, "r");
11921         if(serverMoves != NULL) {
11922             fclose(serverMoves);
11923             /* delay 15 sec before overwriting, so all clients can see end */
11924             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11925         }
11926         serverMoves = fopen(appData.serverMovesName, "w");
11927     }
11928
11929     ExitAnalyzeMode();
11930     gameMode = BeginningOfGame;
11931     ModeHighlight();
11932     if(appData.icsActive) gameInfo.variant = VariantNormal;
11933     currentMove = forwardMostMove = backwardMostMove = 0;
11934     MarkTargetSquares(1);
11935     InitPosition(redraw);
11936     for (i = 0; i < MAX_MOVES; i++) {
11937         if (commentList[i] != NULL) {
11938             free(commentList[i]);
11939             commentList[i] = NULL;
11940         }
11941     }
11942     ResetClocks();
11943     timeRemaining[0][0] = whiteTimeRemaining;
11944     timeRemaining[1][0] = blackTimeRemaining;
11945
11946     if (first.pr == NoProc) {
11947         StartChessProgram(&first);
11948     }
11949     if (init) {
11950             InitChessProgram(&first, startedFromSetupPosition);
11951     }
11952     DisplayTitle("");
11953     DisplayMessage("", "");
11954     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11955     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11956     ClearMap();        // [HGM] exclude: invalidate map
11957 }
11958
11959 void
11960 AutoPlayGameLoop ()
11961 {
11962     for (;;) {
11963         if (!AutoPlayOneMove())
11964           return;
11965         if (matchMode || appData.timeDelay == 0)
11966           continue;
11967         if (appData.timeDelay < 0)
11968           return;
11969         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11970         break;
11971     }
11972 }
11973
11974 void
11975 AnalyzeNextGame()
11976 {
11977     ReloadGame(1); // next game
11978 }
11979
11980 int
11981 AutoPlayOneMove ()
11982 {
11983     int fromX, fromY, toX, toY;
11984
11985     if (appData.debugMode) {
11986       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11987     }
11988
11989     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11990       return FALSE;
11991
11992     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11993       pvInfoList[currentMove].depth = programStats.depth;
11994       pvInfoList[currentMove].score = programStats.score;
11995       pvInfoList[currentMove].time  = 0;
11996       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11997       else { // append analysis of final position as comment
11998         char buf[MSG_SIZ];
11999         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12000         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12001       }
12002       programStats.depth = 0;
12003     }
12004
12005     if (currentMove >= forwardMostMove) {
12006       if(gameMode == AnalyzeFile) {
12007           if(appData.loadGameIndex == -1) {
12008             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12009           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12010           } else {
12011           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12012         }
12013       }
12014 //      gameMode = EndOfGame;
12015 //      ModeHighlight();
12016
12017       /* [AS] Clear current move marker at the end of a game */
12018       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12019
12020       return FALSE;
12021     }
12022
12023     toX = moveList[currentMove][2] - AAA;
12024     toY = moveList[currentMove][3] - ONE;
12025
12026     if (moveList[currentMove][1] == '@') {
12027         if (appData.highlightLastMove) {
12028             SetHighlights(-1, -1, toX, toY);
12029         }
12030     } else {
12031         int viaX = moveList[currentMove][5] - AAA;
12032         int viaY = moveList[currentMove][6] - ONE;
12033         fromX = moveList[currentMove][0] - AAA;
12034         fromY = moveList[currentMove][1] - ONE;
12035
12036         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12037
12038         if(moveList[currentMove][4] == ';') { // multi-leg
12039             ChessSquare piece = boards[currentMove][viaY][viaX];
12040             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
12041             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
12042             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
12043             boards[currentMove][viaY][viaX] = piece;
12044         } else
12045         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12046
12047         if (appData.highlightLastMove) {
12048             SetHighlights(fromX, fromY, toX, toY);
12049         }
12050     }
12051     DisplayMove(currentMove);
12052     SendMoveToProgram(currentMove++, &first);
12053     DisplayBothClocks();
12054     DrawPosition(FALSE, boards[currentMove]);
12055     // [HGM] PV info: always display, routine tests if empty
12056     DisplayComment(currentMove - 1, commentList[currentMove]);
12057     return TRUE;
12058 }
12059
12060
12061 int
12062 LoadGameOneMove (ChessMove readAhead)
12063 {
12064     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12065     char promoChar = NULLCHAR;
12066     ChessMove moveType;
12067     char move[MSG_SIZ];
12068     char *p, *q;
12069
12070     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12071         gameMode != AnalyzeMode && gameMode != Training) {
12072         gameFileFP = NULL;
12073         return FALSE;
12074     }
12075
12076     yyboardindex = forwardMostMove;
12077     if (readAhead != EndOfFile) {
12078       moveType = readAhead;
12079     } else {
12080       if (gameFileFP == NULL)
12081           return FALSE;
12082       moveType = (ChessMove) Myylex();
12083     }
12084
12085     done = FALSE;
12086     switch (moveType) {
12087       case Comment:
12088         if (appData.debugMode)
12089           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12090         p = yy_text;
12091
12092         /* append the comment but don't display it */
12093         AppendComment(currentMove, p, FALSE);
12094         return TRUE;
12095
12096       case WhiteCapturesEnPassant:
12097       case BlackCapturesEnPassant:
12098       case WhitePromotion:
12099       case BlackPromotion:
12100       case WhiteNonPromotion:
12101       case BlackNonPromotion:
12102       case NormalMove:
12103       case FirstLeg:
12104       case WhiteKingSideCastle:
12105       case WhiteQueenSideCastle:
12106       case BlackKingSideCastle:
12107       case BlackQueenSideCastle:
12108       case WhiteKingSideCastleWild:
12109       case WhiteQueenSideCastleWild:
12110       case BlackKingSideCastleWild:
12111       case BlackQueenSideCastleWild:
12112       /* PUSH Fabien */
12113       case WhiteHSideCastleFR:
12114       case WhiteASideCastleFR:
12115       case BlackHSideCastleFR:
12116       case BlackASideCastleFR:
12117       /* POP Fabien */
12118         if (appData.debugMode)
12119           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12120         fromX = currentMoveString[0] - AAA;
12121         fromY = currentMoveString[1] - ONE;
12122         toX = currentMoveString[2] - AAA;
12123         toY = currentMoveString[3] - ONE;
12124         promoChar = currentMoveString[4];
12125         if(promoChar == ';') promoChar = NULLCHAR;
12126         break;
12127
12128       case WhiteDrop:
12129       case BlackDrop:
12130         if (appData.debugMode)
12131           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12132         fromX = moveType == WhiteDrop ?
12133           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12134         (int) CharToPiece(ToLower(currentMoveString[0]));
12135         fromY = DROP_RANK;
12136         toX = currentMoveString[2] - AAA;
12137         toY = currentMoveString[3] - ONE;
12138         break;
12139
12140       case WhiteWins:
12141       case BlackWins:
12142       case GameIsDrawn:
12143       case GameUnfinished:
12144         if (appData.debugMode)
12145           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12146         p = strchr(yy_text, '{');
12147         if (p == NULL) p = strchr(yy_text, '(');
12148         if (p == NULL) {
12149             p = yy_text;
12150             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12151         } else {
12152             q = strchr(p, *p == '{' ? '}' : ')');
12153             if (q != NULL) *q = NULLCHAR;
12154             p++;
12155         }
12156         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12157         GameEnds(moveType, p, GE_FILE);
12158         done = TRUE;
12159         if (cmailMsgLoaded) {
12160             ClearHighlights();
12161             flipView = WhiteOnMove(currentMove);
12162             if (moveType == GameUnfinished) flipView = !flipView;
12163             if (appData.debugMode)
12164               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12165         }
12166         break;
12167
12168       case EndOfFile:
12169         if (appData.debugMode)
12170           fprintf(debugFP, "Parser hit end of file\n");
12171         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12172           case MT_NONE:
12173           case MT_CHECK:
12174             break;
12175           case MT_CHECKMATE:
12176           case MT_STAINMATE:
12177             if (WhiteOnMove(currentMove)) {
12178                 GameEnds(BlackWins, "Black mates", GE_FILE);
12179             } else {
12180                 GameEnds(WhiteWins, "White mates", GE_FILE);
12181             }
12182             break;
12183           case MT_STALEMATE:
12184             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12185             break;
12186         }
12187         done = TRUE;
12188         break;
12189
12190       case MoveNumberOne:
12191         if (lastLoadGameStart == GNUChessGame) {
12192             /* GNUChessGames have numbers, but they aren't move numbers */
12193             if (appData.debugMode)
12194               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12195                       yy_text, (int) moveType);
12196             return LoadGameOneMove(EndOfFile); /* tail recursion */
12197         }
12198         /* else fall thru */
12199
12200       case XBoardGame:
12201       case GNUChessGame:
12202       case PGNTag:
12203         /* Reached start of next game in file */
12204         if (appData.debugMode)
12205           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12206         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12207           case MT_NONE:
12208           case MT_CHECK:
12209             break;
12210           case MT_CHECKMATE:
12211           case MT_STAINMATE:
12212             if (WhiteOnMove(currentMove)) {
12213                 GameEnds(BlackWins, "Black mates", GE_FILE);
12214             } else {
12215                 GameEnds(WhiteWins, "White mates", GE_FILE);
12216             }
12217             break;
12218           case MT_STALEMATE:
12219             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12220             break;
12221         }
12222         done = TRUE;
12223         break;
12224
12225       case PositionDiagram:     /* should not happen; ignore */
12226       case ElapsedTime:         /* ignore */
12227       case NAG:                 /* ignore */
12228         if (appData.debugMode)
12229           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12230                   yy_text, (int) moveType);
12231         return LoadGameOneMove(EndOfFile); /* tail recursion */
12232
12233       case IllegalMove:
12234         if (appData.testLegality) {
12235             if (appData.debugMode)
12236               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12237             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12238                     (forwardMostMove / 2) + 1,
12239                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12240             DisplayError(move, 0);
12241             done = TRUE;
12242         } else {
12243             if (appData.debugMode)
12244               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12245                       yy_text, currentMoveString);
12246             if(currentMoveString[1] == '@') {
12247                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12248                 fromY = DROP_RANK;
12249             } else {
12250                 fromX = currentMoveString[0] - AAA;
12251                 fromY = currentMoveString[1] - ONE;
12252             }
12253             toX = currentMoveString[2] - AAA;
12254             toY = currentMoveString[3] - ONE;
12255             promoChar = currentMoveString[4];
12256         }
12257         break;
12258
12259       case AmbiguousMove:
12260         if (appData.debugMode)
12261           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12262         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12263                 (forwardMostMove / 2) + 1,
12264                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12265         DisplayError(move, 0);
12266         done = TRUE;
12267         break;
12268
12269       default:
12270       case ImpossibleMove:
12271         if (appData.debugMode)
12272           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12273         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12274                 (forwardMostMove / 2) + 1,
12275                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12276         DisplayError(move, 0);
12277         done = TRUE;
12278         break;
12279     }
12280
12281     if (done) {
12282         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12283             DrawPosition(FALSE, boards[currentMove]);
12284             DisplayBothClocks();
12285             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12286               DisplayComment(currentMove - 1, commentList[currentMove]);
12287         }
12288         (void) StopLoadGameTimer();
12289         gameFileFP = NULL;
12290         cmailOldMove = forwardMostMove;
12291         return FALSE;
12292     } else {
12293         /* currentMoveString is set as a side-effect of yylex */
12294
12295         thinkOutput[0] = NULLCHAR;
12296         MakeMove(fromX, fromY, toX, toY, promoChar);
12297         killX = killY = -1; // [HGM] lion: used up
12298         currentMove = forwardMostMove;
12299         return TRUE;
12300     }
12301 }
12302
12303 /* Load the nth game from the given file */
12304 int
12305 LoadGameFromFile (char *filename, int n, char *title, int useList)
12306 {
12307     FILE *f;
12308     char buf[MSG_SIZ];
12309
12310     if (strcmp(filename, "-") == 0) {
12311         f = stdin;
12312         title = "stdin";
12313     } else {
12314         f = fopen(filename, "rb");
12315         if (f == NULL) {
12316           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12317             DisplayError(buf, errno);
12318             return FALSE;
12319         }
12320     }
12321     if (fseek(f, 0, 0) == -1) {
12322         /* f is not seekable; probably a pipe */
12323         useList = FALSE;
12324     }
12325     if (useList && n == 0) {
12326         int error = GameListBuild(f);
12327         if (error) {
12328             DisplayError(_("Cannot build game list"), error);
12329         } else if (!ListEmpty(&gameList) &&
12330                    ((ListGame *) gameList.tailPred)->number > 1) {
12331             GameListPopUp(f, title);
12332             return TRUE;
12333         }
12334         GameListDestroy();
12335         n = 1;
12336     }
12337     if (n == 0) n = 1;
12338     return LoadGame(f, n, title, FALSE);
12339 }
12340
12341
12342 void
12343 MakeRegisteredMove ()
12344 {
12345     int fromX, fromY, toX, toY;
12346     char promoChar;
12347     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12348         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12349           case CMAIL_MOVE:
12350           case CMAIL_DRAW:
12351             if (appData.debugMode)
12352               fprintf(debugFP, "Restoring %s for game %d\n",
12353                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12354
12355             thinkOutput[0] = NULLCHAR;
12356             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12357             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12358             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12359             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12360             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12361             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12362             MakeMove(fromX, fromY, toX, toY, promoChar);
12363             ShowMove(fromX, fromY, toX, toY);
12364
12365             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12366               case MT_NONE:
12367               case MT_CHECK:
12368                 break;
12369
12370               case MT_CHECKMATE:
12371               case MT_STAINMATE:
12372                 if (WhiteOnMove(currentMove)) {
12373                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12374                 } else {
12375                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12376                 }
12377                 break;
12378
12379               case MT_STALEMATE:
12380                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12381                 break;
12382             }
12383
12384             break;
12385
12386           case CMAIL_RESIGN:
12387             if (WhiteOnMove(currentMove)) {
12388                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12389             } else {
12390                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12391             }
12392             break;
12393
12394           case CMAIL_ACCEPT:
12395             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12396             break;
12397
12398           default:
12399             break;
12400         }
12401     }
12402
12403     return;
12404 }
12405
12406 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12407 int
12408 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12409 {
12410     int retVal;
12411
12412     if (gameNumber > nCmailGames) {
12413         DisplayError(_("No more games in this message"), 0);
12414         return FALSE;
12415     }
12416     if (f == lastLoadGameFP) {
12417         int offset = gameNumber - lastLoadGameNumber;
12418         if (offset == 0) {
12419             cmailMsg[0] = NULLCHAR;
12420             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12421                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12422                 nCmailMovesRegistered--;
12423             }
12424             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12425             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12426                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12427             }
12428         } else {
12429             if (! RegisterMove()) return FALSE;
12430         }
12431     }
12432
12433     retVal = LoadGame(f, gameNumber, title, useList);
12434
12435     /* Make move registered during previous look at this game, if any */
12436     MakeRegisteredMove();
12437
12438     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12439         commentList[currentMove]
12440           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12441         DisplayComment(currentMove - 1, commentList[currentMove]);
12442     }
12443
12444     return retVal;
12445 }
12446
12447 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12448 int
12449 ReloadGame (int offset)
12450 {
12451     int gameNumber = lastLoadGameNumber + offset;
12452     if (lastLoadGameFP == NULL) {
12453         DisplayError(_("No game has been loaded yet"), 0);
12454         return FALSE;
12455     }
12456     if (gameNumber <= 0) {
12457         DisplayError(_("Can't back up any further"), 0);
12458         return FALSE;
12459     }
12460     if (cmailMsgLoaded) {
12461         return CmailLoadGame(lastLoadGameFP, gameNumber,
12462                              lastLoadGameTitle, lastLoadGameUseList);
12463     } else {
12464         return LoadGame(lastLoadGameFP, gameNumber,
12465                         lastLoadGameTitle, lastLoadGameUseList);
12466     }
12467 }
12468
12469 int keys[EmptySquare+1];
12470
12471 int
12472 PositionMatches (Board b1, Board b2)
12473 {
12474     int r, f, sum=0;
12475     switch(appData.searchMode) {
12476         case 1: return CompareWithRights(b1, b2);
12477         case 2:
12478             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12479                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12480             }
12481             return TRUE;
12482         case 3:
12483             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12484               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12485                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12486             }
12487             return sum==0;
12488         case 4:
12489             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12490                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12491             }
12492             return sum==0;
12493     }
12494     return TRUE;
12495 }
12496
12497 #define Q_PROMO  4
12498 #define Q_EP     3
12499 #define Q_BCASTL 2
12500 #define Q_WCASTL 1
12501
12502 int pieceList[256], quickBoard[256];
12503 ChessSquare pieceType[256] = { EmptySquare };
12504 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12505 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12506 int soughtTotal, turn;
12507 Boolean epOK, flipSearch;
12508
12509 typedef struct {
12510     unsigned char piece, to;
12511 } Move;
12512
12513 #define DSIZE (250000)
12514
12515 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12516 Move *moveDatabase = initialSpace;
12517 unsigned int movePtr, dataSize = DSIZE;
12518
12519 int
12520 MakePieceList (Board board, int *counts)
12521 {
12522     int r, f, n=Q_PROMO, total=0;
12523     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12524     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12525         int sq = f + (r<<4);
12526         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12527             quickBoard[sq] = ++n;
12528             pieceList[n] = sq;
12529             pieceType[n] = board[r][f];
12530             counts[board[r][f]]++;
12531             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12532             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12533             total++;
12534         }
12535     }
12536     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12537     return total;
12538 }
12539
12540 void
12541 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12542 {
12543     int sq = fromX + (fromY<<4);
12544     int piece = quickBoard[sq], rook;
12545     quickBoard[sq] = 0;
12546     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12547     if(piece == pieceList[1] && fromY == toY) {
12548       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12549         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12550         moveDatabase[movePtr++].piece = Q_WCASTL;
12551         quickBoard[sq] = piece;
12552         piece = quickBoard[from]; quickBoard[from] = 0;
12553         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12554       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12555         quickBoard[sq] = 0; // remove Rook
12556         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12557         moveDatabase[movePtr++].piece = Q_WCASTL;
12558         quickBoard[sq] = pieceList[1]; // put King
12559         piece = rook;
12560         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12561       }
12562     } else
12563     if(piece == pieceList[2] && fromY == toY) {
12564       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12565         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12566         moveDatabase[movePtr++].piece = Q_BCASTL;
12567         quickBoard[sq] = piece;
12568         piece = quickBoard[from]; quickBoard[from] = 0;
12569         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12570       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12571         quickBoard[sq] = 0; // remove Rook
12572         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12573         moveDatabase[movePtr++].piece = Q_BCASTL;
12574         quickBoard[sq] = pieceList[2]; // put King
12575         piece = rook;
12576         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12577       }
12578     } else
12579     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12580         quickBoard[(fromY<<4)+toX] = 0;
12581         moveDatabase[movePtr].piece = Q_EP;
12582         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12583         moveDatabase[movePtr].to = sq;
12584     } else
12585     if(promoPiece != pieceType[piece]) {
12586         moveDatabase[movePtr++].piece = Q_PROMO;
12587         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12588     }
12589     moveDatabase[movePtr].piece = piece;
12590     quickBoard[sq] = piece;
12591     movePtr++;
12592 }
12593
12594 int
12595 PackGame (Board board)
12596 {
12597     Move *newSpace = NULL;
12598     moveDatabase[movePtr].piece = 0; // terminate previous game
12599     if(movePtr > dataSize) {
12600         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12601         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12602         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12603         if(newSpace) {
12604             int i;
12605             Move *p = moveDatabase, *q = newSpace;
12606             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12607             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12608             moveDatabase = newSpace;
12609         } else { // calloc failed, we must be out of memory. Too bad...
12610             dataSize = 0; // prevent calloc events for all subsequent games
12611             return 0;     // and signal this one isn't cached
12612         }
12613     }
12614     movePtr++;
12615     MakePieceList(board, counts);
12616     return movePtr;
12617 }
12618
12619 int
12620 QuickCompare (Board board, int *minCounts, int *maxCounts)
12621 {   // compare according to search mode
12622     int r, f;
12623     switch(appData.searchMode)
12624     {
12625       case 1: // exact position match
12626         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12627         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12628             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12629         }
12630         break;
12631       case 2: // can have extra material on empty squares
12632         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12633             if(board[r][f] == EmptySquare) continue;
12634             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12635         }
12636         break;
12637       case 3: // material with exact Pawn structure
12638         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12639             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12640             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12641         } // fall through to material comparison
12642       case 4: // exact material
12643         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12644         break;
12645       case 6: // material range with given imbalance
12646         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12647         // fall through to range comparison
12648       case 5: // material range
12649         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12650     }
12651     return TRUE;
12652 }
12653
12654 int
12655 QuickScan (Board board, Move *move)
12656 {   // reconstruct game,and compare all positions in it
12657     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12658     do {
12659         int piece = move->piece;
12660         int to = move->to, from = pieceList[piece];
12661         if(found < 0) { // if already found just scan to game end for final piece count
12662           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12663            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12664            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12665                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12666             ) {
12667             static int lastCounts[EmptySquare+1];
12668             int i;
12669             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12670             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12671           } else stretch = 0;
12672           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12673           if(found >= 0 && !appData.minPieces) return found;
12674         }
12675         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12676           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12677           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12678             piece = (++move)->piece;
12679             from = pieceList[piece];
12680             counts[pieceType[piece]]--;
12681             pieceType[piece] = (ChessSquare) move->to;
12682             counts[move->to]++;
12683           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12684             counts[pieceType[quickBoard[to]]]--;
12685             quickBoard[to] = 0; total--;
12686             move++;
12687             continue;
12688           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12689             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12690             from  = pieceList[piece]; // so this must be King
12691             quickBoard[from] = 0;
12692             pieceList[piece] = to;
12693             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12694             quickBoard[from] = 0; // rook
12695             quickBoard[to] = piece;
12696             to = move->to; piece = move->piece;
12697             goto aftercastle;
12698           }
12699         }
12700         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12701         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12702         quickBoard[from] = 0;
12703       aftercastle:
12704         quickBoard[to] = piece;
12705         pieceList[piece] = to;
12706         cnt++; turn ^= 3;
12707         move++;
12708     } while(1);
12709 }
12710
12711 void
12712 InitSearch ()
12713 {
12714     int r, f;
12715     flipSearch = FALSE;
12716     CopyBoard(soughtBoard, boards[currentMove]);
12717     soughtTotal = MakePieceList(soughtBoard, maxSought);
12718     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12719     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12720     CopyBoard(reverseBoard, boards[currentMove]);
12721     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12722         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12723         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12724         reverseBoard[r][f] = piece;
12725     }
12726     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12727     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12728     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12729                  || (boards[currentMove][CASTLING][2] == NoRights ||
12730                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12731                  && (boards[currentMove][CASTLING][5] == NoRights ||
12732                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12733       ) {
12734         flipSearch = TRUE;
12735         CopyBoard(flipBoard, soughtBoard);
12736         CopyBoard(rotateBoard, reverseBoard);
12737         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12738             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12739             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12740         }
12741     }
12742     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12743     if(appData.searchMode >= 5) {
12744         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12745         MakePieceList(soughtBoard, minSought);
12746         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12747     }
12748     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12749         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12750 }
12751
12752 GameInfo dummyInfo;
12753 static int creatingBook;
12754
12755 int
12756 GameContainsPosition (FILE *f, ListGame *lg)
12757 {
12758     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12759     int fromX, fromY, toX, toY;
12760     char promoChar;
12761     static int initDone=FALSE;
12762
12763     // weed out games based on numerical tag comparison
12764     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12765     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12766     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12767     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12768     if(!initDone) {
12769         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12770         initDone = TRUE;
12771     }
12772     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12773     else CopyBoard(boards[scratch], initialPosition); // default start position
12774     if(lg->moves) {
12775         turn = btm + 1;
12776         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12777         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12778     }
12779     if(btm) plyNr++;
12780     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12781     fseek(f, lg->offset, 0);
12782     yynewfile(f);
12783     while(1) {
12784         yyboardindex = scratch;
12785         quickFlag = plyNr+1;
12786         next = Myylex();
12787         quickFlag = 0;
12788         switch(next) {
12789             case PGNTag:
12790                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12791             default:
12792                 continue;
12793
12794             case XBoardGame:
12795             case GNUChessGame:
12796                 if(plyNr) return -1; // after we have seen moves, this is for new game
12797               continue;
12798
12799             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12800             case ImpossibleMove:
12801             case WhiteWins: // game ends here with these four
12802             case BlackWins:
12803             case GameIsDrawn:
12804             case GameUnfinished:
12805                 return -1;
12806
12807             case IllegalMove:
12808                 if(appData.testLegality) return -1;
12809             case WhiteCapturesEnPassant:
12810             case BlackCapturesEnPassant:
12811             case WhitePromotion:
12812             case BlackPromotion:
12813             case WhiteNonPromotion:
12814             case BlackNonPromotion:
12815             case NormalMove:
12816             case FirstLeg:
12817             case WhiteKingSideCastle:
12818             case WhiteQueenSideCastle:
12819             case BlackKingSideCastle:
12820             case BlackQueenSideCastle:
12821             case WhiteKingSideCastleWild:
12822             case WhiteQueenSideCastleWild:
12823             case BlackKingSideCastleWild:
12824             case BlackQueenSideCastleWild:
12825             case WhiteHSideCastleFR:
12826             case WhiteASideCastleFR:
12827             case BlackHSideCastleFR:
12828             case BlackASideCastleFR:
12829                 fromX = currentMoveString[0] - AAA;
12830                 fromY = currentMoveString[1] - ONE;
12831                 toX = currentMoveString[2] - AAA;
12832                 toY = currentMoveString[3] - ONE;
12833                 promoChar = currentMoveString[4];
12834                 break;
12835             case WhiteDrop:
12836             case BlackDrop:
12837                 fromX = next == WhiteDrop ?
12838                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12839                   (int) CharToPiece(ToLower(currentMoveString[0]));
12840                 fromY = DROP_RANK;
12841                 toX = currentMoveString[2] - AAA;
12842                 toY = currentMoveString[3] - ONE;
12843                 promoChar = 0;
12844                 break;
12845         }
12846         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12847         plyNr++;
12848         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12849         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12850         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12851         if(appData.findMirror) {
12852             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12853             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12854         }
12855     }
12856 }
12857
12858 /* Load the nth game from open file f */
12859 int
12860 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12861 {
12862     ChessMove cm;
12863     char buf[MSG_SIZ];
12864     int gn = gameNumber;
12865     ListGame *lg = NULL;
12866     int numPGNTags = 0;
12867     int err, pos = -1;
12868     GameMode oldGameMode;
12869     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12870     char oldName[MSG_SIZ];
12871
12872     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12873
12874     if (appData.debugMode)
12875         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12876
12877     if (gameMode == Training )
12878         SetTrainingModeOff();
12879
12880     oldGameMode = gameMode;
12881     if (gameMode != BeginningOfGame) {
12882       Reset(FALSE, TRUE);
12883     }
12884     killX = killY = -1; // [HGM] lion: in case we did not Reset
12885
12886     gameFileFP = f;
12887     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12888         fclose(lastLoadGameFP);
12889     }
12890
12891     if (useList) {
12892         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12893
12894         if (lg) {
12895             fseek(f, lg->offset, 0);
12896             GameListHighlight(gameNumber);
12897             pos = lg->position;
12898             gn = 1;
12899         }
12900         else {
12901             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12902               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12903             else
12904             DisplayError(_("Game number out of range"), 0);
12905             return FALSE;
12906         }
12907     } else {
12908         GameListDestroy();
12909         if (fseek(f, 0, 0) == -1) {
12910             if (f == lastLoadGameFP ?
12911                 gameNumber == lastLoadGameNumber + 1 :
12912                 gameNumber == 1) {
12913                 gn = 1;
12914             } else {
12915                 DisplayError(_("Can't seek on game file"), 0);
12916                 return FALSE;
12917             }
12918         }
12919     }
12920     lastLoadGameFP = f;
12921     lastLoadGameNumber = gameNumber;
12922     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12923     lastLoadGameUseList = useList;
12924
12925     yynewfile(f);
12926
12927     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12928       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12929                 lg->gameInfo.black);
12930             DisplayTitle(buf);
12931     } else if (*title != NULLCHAR) {
12932         if (gameNumber > 1) {
12933           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12934             DisplayTitle(buf);
12935         } else {
12936             DisplayTitle(title);
12937         }
12938     }
12939
12940     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12941         gameMode = PlayFromGameFile;
12942         ModeHighlight();
12943     }
12944
12945     currentMove = forwardMostMove = backwardMostMove = 0;
12946     CopyBoard(boards[0], initialPosition);
12947     StopClocks();
12948
12949     /*
12950      * Skip the first gn-1 games in the file.
12951      * Also skip over anything that precedes an identifiable
12952      * start of game marker, to avoid being confused by
12953      * garbage at the start of the file.  Currently
12954      * recognized start of game markers are the move number "1",
12955      * the pattern "gnuchess .* game", the pattern
12956      * "^[#;%] [^ ]* game file", and a PGN tag block.
12957      * A game that starts with one of the latter two patterns
12958      * will also have a move number 1, possibly
12959      * following a position diagram.
12960      * 5-4-02: Let's try being more lenient and allowing a game to
12961      * start with an unnumbered move.  Does that break anything?
12962      */
12963     cm = lastLoadGameStart = EndOfFile;
12964     while (gn > 0) {
12965         yyboardindex = forwardMostMove;
12966         cm = (ChessMove) Myylex();
12967         switch (cm) {
12968           case EndOfFile:
12969             if (cmailMsgLoaded) {
12970                 nCmailGames = CMAIL_MAX_GAMES - gn;
12971             } else {
12972                 Reset(TRUE, TRUE);
12973                 DisplayError(_("Game not found in file"), 0);
12974             }
12975             return FALSE;
12976
12977           case GNUChessGame:
12978           case XBoardGame:
12979             gn--;
12980             lastLoadGameStart = cm;
12981             break;
12982
12983           case MoveNumberOne:
12984             switch (lastLoadGameStart) {
12985               case GNUChessGame:
12986               case XBoardGame:
12987               case PGNTag:
12988                 break;
12989               case MoveNumberOne:
12990               case EndOfFile:
12991                 gn--;           /* count this game */
12992                 lastLoadGameStart = cm;
12993                 break;
12994               default:
12995                 /* impossible */
12996                 break;
12997             }
12998             break;
12999
13000           case PGNTag:
13001             switch (lastLoadGameStart) {
13002               case GNUChessGame:
13003               case PGNTag:
13004               case MoveNumberOne:
13005               case EndOfFile:
13006                 gn--;           /* count this game */
13007                 lastLoadGameStart = cm;
13008                 break;
13009               case XBoardGame:
13010                 lastLoadGameStart = cm; /* game counted already */
13011                 break;
13012               default:
13013                 /* impossible */
13014                 break;
13015             }
13016             if (gn > 0) {
13017                 do {
13018                     yyboardindex = forwardMostMove;
13019                     cm = (ChessMove) Myylex();
13020                 } while (cm == PGNTag || cm == Comment);
13021             }
13022             break;
13023
13024           case WhiteWins:
13025           case BlackWins:
13026           case GameIsDrawn:
13027             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13028                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13029                     != CMAIL_OLD_RESULT) {
13030                     nCmailResults ++ ;
13031                     cmailResult[  CMAIL_MAX_GAMES
13032                                 - gn - 1] = CMAIL_OLD_RESULT;
13033                 }
13034             }
13035             break;
13036
13037           case NormalMove:
13038           case FirstLeg:
13039             /* Only a NormalMove can be at the start of a game
13040              * without a position diagram. */
13041             if (lastLoadGameStart == EndOfFile ) {
13042               gn--;
13043               lastLoadGameStart = MoveNumberOne;
13044             }
13045             break;
13046
13047           default:
13048             break;
13049         }
13050     }
13051
13052     if (appData.debugMode)
13053       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13054
13055     if (cm == XBoardGame) {
13056         /* Skip any header junk before position diagram and/or move 1 */
13057         for (;;) {
13058             yyboardindex = forwardMostMove;
13059             cm = (ChessMove) Myylex();
13060
13061             if (cm == EndOfFile ||
13062                 cm == GNUChessGame || cm == XBoardGame) {
13063                 /* Empty game; pretend end-of-file and handle later */
13064                 cm = EndOfFile;
13065                 break;
13066             }
13067
13068             if (cm == MoveNumberOne || cm == PositionDiagram ||
13069                 cm == PGNTag || cm == Comment)
13070               break;
13071         }
13072     } else if (cm == GNUChessGame) {
13073         if (gameInfo.event != NULL) {
13074             free(gameInfo.event);
13075         }
13076         gameInfo.event = StrSave(yy_text);
13077     }
13078
13079     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13080     while (cm == PGNTag) {
13081         if (appData.debugMode)
13082           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13083         err = ParsePGNTag(yy_text, &gameInfo);
13084         if (!err) numPGNTags++;
13085
13086         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13087         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13088             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13089             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13090             InitPosition(TRUE);
13091             oldVariant = gameInfo.variant;
13092             if (appData.debugMode)
13093               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13094         }
13095
13096
13097         if (gameInfo.fen != NULL) {
13098           Board initial_position;
13099           startedFromSetupPosition = TRUE;
13100           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13101             Reset(TRUE, TRUE);
13102             DisplayError(_("Bad FEN position in file"), 0);
13103             return FALSE;
13104           }
13105           CopyBoard(boards[0], initial_position);
13106           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13107             CopyBoard(initialPosition, initial_position);
13108           if (blackPlaysFirst) {
13109             currentMove = forwardMostMove = backwardMostMove = 1;
13110             CopyBoard(boards[1], initial_position);
13111             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13112             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13113             timeRemaining[0][1] = whiteTimeRemaining;
13114             timeRemaining[1][1] = blackTimeRemaining;
13115             if (commentList[0] != NULL) {
13116               commentList[1] = commentList[0];
13117               commentList[0] = NULL;
13118             }
13119           } else {
13120             currentMove = forwardMostMove = backwardMostMove = 0;
13121           }
13122           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13123           {   int i;
13124               initialRulePlies = FENrulePlies;
13125               for( i=0; i< nrCastlingRights; i++ )
13126                   initialRights[i] = initial_position[CASTLING][i];
13127           }
13128           yyboardindex = forwardMostMove;
13129           free(gameInfo.fen);
13130           gameInfo.fen = NULL;
13131         }
13132
13133         yyboardindex = forwardMostMove;
13134         cm = (ChessMove) Myylex();
13135
13136         /* Handle comments interspersed among the tags */
13137         while (cm == Comment) {
13138             char *p;
13139             if (appData.debugMode)
13140               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13141             p = yy_text;
13142             AppendComment(currentMove, p, FALSE);
13143             yyboardindex = forwardMostMove;
13144             cm = (ChessMove) Myylex();
13145         }
13146     }
13147
13148     /* don't rely on existence of Event tag since if game was
13149      * pasted from clipboard the Event tag may not exist
13150      */
13151     if (numPGNTags > 0){
13152         char *tags;
13153         if (gameInfo.variant == VariantNormal) {
13154           VariantClass v = StringToVariant(gameInfo.event);
13155           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13156           if(v < VariantShogi) gameInfo.variant = v;
13157         }
13158         if (!matchMode) {
13159           if( appData.autoDisplayTags ) {
13160             tags = PGNTags(&gameInfo);
13161             TagsPopUp(tags, CmailMsg());
13162             free(tags);
13163           }
13164         }
13165     } else {
13166         /* Make something up, but don't display it now */
13167         SetGameInfo();
13168         TagsPopDown();
13169     }
13170
13171     if (cm == PositionDiagram) {
13172         int i, j;
13173         char *p;
13174         Board initial_position;
13175
13176         if (appData.debugMode)
13177           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13178
13179         if (!startedFromSetupPosition) {
13180             p = yy_text;
13181             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13182               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13183                 switch (*p) {
13184                   case '{':
13185                   case '[':
13186                   case '-':
13187                   case ' ':
13188                   case '\t':
13189                   case '\n':
13190                   case '\r':
13191                     break;
13192                   default:
13193                     initial_position[i][j++] = CharToPiece(*p);
13194                     break;
13195                 }
13196             while (*p == ' ' || *p == '\t' ||
13197                    *p == '\n' || *p == '\r') p++;
13198
13199             if (strncmp(p, "black", strlen("black"))==0)
13200               blackPlaysFirst = TRUE;
13201             else
13202               blackPlaysFirst = FALSE;
13203             startedFromSetupPosition = TRUE;
13204
13205             CopyBoard(boards[0], initial_position);
13206             if (blackPlaysFirst) {
13207                 currentMove = forwardMostMove = backwardMostMove = 1;
13208                 CopyBoard(boards[1], initial_position);
13209                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13210                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13211                 timeRemaining[0][1] = whiteTimeRemaining;
13212                 timeRemaining[1][1] = blackTimeRemaining;
13213                 if (commentList[0] != NULL) {
13214                     commentList[1] = commentList[0];
13215                     commentList[0] = NULL;
13216                 }
13217             } else {
13218                 currentMove = forwardMostMove = backwardMostMove = 0;
13219             }
13220         }
13221         yyboardindex = forwardMostMove;
13222         cm = (ChessMove) Myylex();
13223     }
13224
13225   if(!creatingBook) {
13226     if (first.pr == NoProc) {
13227         StartChessProgram(&first);
13228     }
13229     InitChessProgram(&first, FALSE);
13230     if(gameInfo.variant == VariantUnknown && *oldName) {
13231         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13232         gameInfo.variant = v;
13233     }
13234     SendToProgram("force\n", &first);
13235     if (startedFromSetupPosition) {
13236         SendBoard(&first, forwardMostMove);
13237     if (appData.debugMode) {
13238         fprintf(debugFP, "Load Game\n");
13239     }
13240         DisplayBothClocks();
13241     }
13242   }
13243
13244     /* [HGM] server: flag to write setup moves in broadcast file as one */
13245     loadFlag = appData.suppressLoadMoves;
13246
13247     while (cm == Comment) {
13248         char *p;
13249         if (appData.debugMode)
13250           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13251         p = yy_text;
13252         AppendComment(currentMove, p, FALSE);
13253         yyboardindex = forwardMostMove;
13254         cm = (ChessMove) Myylex();
13255     }
13256
13257     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13258         cm == WhiteWins || cm == BlackWins ||
13259         cm == GameIsDrawn || cm == GameUnfinished) {
13260         DisplayMessage("", _("No moves in game"));
13261         if (cmailMsgLoaded) {
13262             if (appData.debugMode)
13263               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13264             ClearHighlights();
13265             flipView = FALSE;
13266         }
13267         DrawPosition(FALSE, boards[currentMove]);
13268         DisplayBothClocks();
13269         gameMode = EditGame;
13270         ModeHighlight();
13271         gameFileFP = NULL;
13272         cmailOldMove = 0;
13273         return TRUE;
13274     }
13275
13276     // [HGM] PV info: routine tests if comment empty
13277     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13278         DisplayComment(currentMove - 1, commentList[currentMove]);
13279     }
13280     if (!matchMode && appData.timeDelay != 0)
13281       DrawPosition(FALSE, boards[currentMove]);
13282
13283     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13284       programStats.ok_to_send = 1;
13285     }
13286
13287     /* if the first token after the PGN tags is a move
13288      * and not move number 1, retrieve it from the parser
13289      */
13290     if (cm != MoveNumberOne)
13291         LoadGameOneMove(cm);
13292
13293     /* load the remaining moves from the file */
13294     while (LoadGameOneMove(EndOfFile)) {
13295       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13296       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13297     }
13298
13299     /* rewind to the start of the game */
13300     currentMove = backwardMostMove;
13301
13302     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13303
13304     if (oldGameMode == AnalyzeFile) {
13305       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13306       AnalyzeFileEvent();
13307     } else
13308     if (oldGameMode == AnalyzeMode) {
13309       AnalyzeFileEvent();
13310     }
13311
13312     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13313         long int w, b; // [HGM] adjourn: restore saved clock times
13314         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13315         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13316             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13317             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13318         }
13319     }
13320
13321     if(creatingBook) return TRUE;
13322     if (!matchMode && pos > 0) {
13323         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13324     } else
13325     if (matchMode || appData.timeDelay == 0) {
13326       ToEndEvent();
13327     } else if (appData.timeDelay > 0) {
13328       AutoPlayGameLoop();
13329     }
13330
13331     if (appData.debugMode)
13332         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13333
13334     loadFlag = 0; /* [HGM] true game starts */
13335     return TRUE;
13336 }
13337
13338 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13339 int
13340 ReloadPosition (int offset)
13341 {
13342     int positionNumber = lastLoadPositionNumber + offset;
13343     if (lastLoadPositionFP == NULL) {
13344         DisplayError(_("No position has been loaded yet"), 0);
13345         return FALSE;
13346     }
13347     if (positionNumber <= 0) {
13348         DisplayError(_("Can't back up any further"), 0);
13349         return FALSE;
13350     }
13351     return LoadPosition(lastLoadPositionFP, positionNumber,
13352                         lastLoadPositionTitle);
13353 }
13354
13355 /* Load the nth position from the given file */
13356 int
13357 LoadPositionFromFile (char *filename, int n, char *title)
13358 {
13359     FILE *f;
13360     char buf[MSG_SIZ];
13361
13362     if (strcmp(filename, "-") == 0) {
13363         return LoadPosition(stdin, n, "stdin");
13364     } else {
13365         f = fopen(filename, "rb");
13366         if (f == NULL) {
13367             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13368             DisplayError(buf, errno);
13369             return FALSE;
13370         } else {
13371             return LoadPosition(f, n, title);
13372         }
13373     }
13374 }
13375
13376 /* Load the nth position from the given open file, and close it */
13377 int
13378 LoadPosition (FILE *f, int positionNumber, char *title)
13379 {
13380     char *p, line[MSG_SIZ];
13381     Board initial_position;
13382     int i, j, fenMode, pn;
13383
13384     if (gameMode == Training )
13385         SetTrainingModeOff();
13386
13387     if (gameMode != BeginningOfGame) {
13388         Reset(FALSE, TRUE);
13389     }
13390     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13391         fclose(lastLoadPositionFP);
13392     }
13393     if (positionNumber == 0) positionNumber = 1;
13394     lastLoadPositionFP = f;
13395     lastLoadPositionNumber = positionNumber;
13396     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13397     if (first.pr == NoProc && !appData.noChessProgram) {
13398       StartChessProgram(&first);
13399       InitChessProgram(&first, FALSE);
13400     }
13401     pn = positionNumber;
13402     if (positionNumber < 0) {
13403         /* Negative position number means to seek to that byte offset */
13404         if (fseek(f, -positionNumber, 0) == -1) {
13405             DisplayError(_("Can't seek on position file"), 0);
13406             return FALSE;
13407         };
13408         pn = 1;
13409     } else {
13410         if (fseek(f, 0, 0) == -1) {
13411             if (f == lastLoadPositionFP ?
13412                 positionNumber == lastLoadPositionNumber + 1 :
13413                 positionNumber == 1) {
13414                 pn = 1;
13415             } else {
13416                 DisplayError(_("Can't seek on position file"), 0);
13417                 return FALSE;
13418             }
13419         }
13420     }
13421     /* See if this file is FEN or old-style xboard */
13422     if (fgets(line, MSG_SIZ, f) == NULL) {
13423         DisplayError(_("Position not found in file"), 0);
13424         return FALSE;
13425     }
13426     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13427     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13428
13429     if (pn >= 2) {
13430         if (fenMode || line[0] == '#') pn--;
13431         while (pn > 0) {
13432             /* skip positions before number pn */
13433             if (fgets(line, MSG_SIZ, f) == NULL) {
13434                 Reset(TRUE, TRUE);
13435                 DisplayError(_("Position not found in file"), 0);
13436                 return FALSE;
13437             }
13438             if (fenMode || line[0] == '#') pn--;
13439         }
13440     }
13441
13442     if (fenMode) {
13443         char *p;
13444         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13445             DisplayError(_("Bad FEN position in file"), 0);
13446             return FALSE;
13447         }
13448         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13449             sscanf(p+3, "%s", bestMove);
13450         } else *bestMove = NULLCHAR;
13451     } else {
13452         (void) fgets(line, MSG_SIZ, f);
13453         (void) fgets(line, MSG_SIZ, f);
13454
13455         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13456             (void) fgets(line, MSG_SIZ, f);
13457             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13458                 if (*p == ' ')
13459                   continue;
13460                 initial_position[i][j++] = CharToPiece(*p);
13461             }
13462         }
13463
13464         blackPlaysFirst = FALSE;
13465         if (!feof(f)) {
13466             (void) fgets(line, MSG_SIZ, f);
13467             if (strncmp(line, "black", strlen("black"))==0)
13468               blackPlaysFirst = TRUE;
13469         }
13470     }
13471     startedFromSetupPosition = TRUE;
13472
13473     CopyBoard(boards[0], initial_position);
13474     if (blackPlaysFirst) {
13475         currentMove = forwardMostMove = backwardMostMove = 1;
13476         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13477         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13478         CopyBoard(boards[1], initial_position);
13479         DisplayMessage("", _("Black to play"));
13480     } else {
13481         currentMove = forwardMostMove = backwardMostMove = 0;
13482         DisplayMessage("", _("White to play"));
13483     }
13484     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13485     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13486         SendToProgram("force\n", &first);
13487         SendBoard(&first, forwardMostMove);
13488     }
13489     if (appData.debugMode) {
13490 int i, j;
13491   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13492   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13493         fprintf(debugFP, "Load Position\n");
13494     }
13495
13496     if (positionNumber > 1) {
13497       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13498         DisplayTitle(line);
13499     } else {
13500         DisplayTitle(title);
13501     }
13502     gameMode = EditGame;
13503     ModeHighlight();
13504     ResetClocks();
13505     timeRemaining[0][1] = whiteTimeRemaining;
13506     timeRemaining[1][1] = blackTimeRemaining;
13507     DrawPosition(FALSE, boards[currentMove]);
13508
13509     return TRUE;
13510 }
13511
13512
13513 void
13514 CopyPlayerNameIntoFileName (char **dest, char *src)
13515 {
13516     while (*src != NULLCHAR && *src != ',') {
13517         if (*src == ' ') {
13518             *(*dest)++ = '_';
13519             src++;
13520         } else {
13521             *(*dest)++ = *src++;
13522         }
13523     }
13524 }
13525
13526 char *
13527 DefaultFileName (char *ext)
13528 {
13529     static char def[MSG_SIZ];
13530     char *p;
13531
13532     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13533         p = def;
13534         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13535         *p++ = '-';
13536         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13537         *p++ = '.';
13538         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13539     } else {
13540         def[0] = NULLCHAR;
13541     }
13542     return def;
13543 }
13544
13545 /* Save the current game to the given file */
13546 int
13547 SaveGameToFile (char *filename, int append)
13548 {
13549     FILE *f;
13550     char buf[MSG_SIZ];
13551     int result, i, t,tot=0;
13552
13553     if (strcmp(filename, "-") == 0) {
13554         return SaveGame(stdout, 0, NULL);
13555     } else {
13556         for(i=0; i<10; i++) { // upto 10 tries
13557              f = fopen(filename, append ? "a" : "w");
13558              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13559              if(f || errno != 13) break;
13560              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13561              tot += t;
13562         }
13563         if (f == NULL) {
13564             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13565             DisplayError(buf, errno);
13566             return FALSE;
13567         } else {
13568             safeStrCpy(buf, lastMsg, MSG_SIZ);
13569             DisplayMessage(_("Waiting for access to save file"), "");
13570             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13571             DisplayMessage(_("Saving game"), "");
13572             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13573             result = SaveGame(f, 0, NULL);
13574             DisplayMessage(buf, "");
13575             return result;
13576         }
13577     }
13578 }
13579
13580 char *
13581 SavePart (char *str)
13582 {
13583     static char buf[MSG_SIZ];
13584     char *p;
13585
13586     p = strchr(str, ' ');
13587     if (p == NULL) return str;
13588     strncpy(buf, str, p - str);
13589     buf[p - str] = NULLCHAR;
13590     return buf;
13591 }
13592
13593 #define PGN_MAX_LINE 75
13594
13595 #define PGN_SIDE_WHITE  0
13596 #define PGN_SIDE_BLACK  1
13597
13598 static int
13599 FindFirstMoveOutOfBook (int side)
13600 {
13601     int result = -1;
13602
13603     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13604         int index = backwardMostMove;
13605         int has_book_hit = 0;
13606
13607         if( (index % 2) != side ) {
13608             index++;
13609         }
13610
13611         while( index < forwardMostMove ) {
13612             /* Check to see if engine is in book */
13613             int depth = pvInfoList[index].depth;
13614             int score = pvInfoList[index].score;
13615             int in_book = 0;
13616
13617             if( depth <= 2 ) {
13618                 in_book = 1;
13619             }
13620             else if( score == 0 && depth == 63 ) {
13621                 in_book = 1; /* Zappa */
13622             }
13623             else if( score == 2 && depth == 99 ) {
13624                 in_book = 1; /* Abrok */
13625             }
13626
13627             has_book_hit += in_book;
13628
13629             if( ! in_book ) {
13630                 result = index;
13631
13632                 break;
13633             }
13634
13635             index += 2;
13636         }
13637     }
13638
13639     return result;
13640 }
13641
13642 void
13643 GetOutOfBookInfo (char * buf)
13644 {
13645     int oob[2];
13646     int i;
13647     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13648
13649     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13650     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13651
13652     *buf = '\0';
13653
13654     if( oob[0] >= 0 || oob[1] >= 0 ) {
13655         for( i=0; i<2; i++ ) {
13656             int idx = oob[i];
13657
13658             if( idx >= 0 ) {
13659                 if( i > 0 && oob[0] >= 0 ) {
13660                     strcat( buf, "   " );
13661                 }
13662
13663                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13664                 sprintf( buf+strlen(buf), "%s%.2f",
13665                     pvInfoList[idx].score >= 0 ? "+" : "",
13666                     pvInfoList[idx].score / 100.0 );
13667             }
13668         }
13669     }
13670 }
13671
13672 /* Save game in PGN style */
13673 static void
13674 SaveGamePGN2 (FILE *f)
13675 {
13676     int i, offset, linelen, newblock;
13677 //    char *movetext;
13678     char numtext[32];
13679     int movelen, numlen, blank;
13680     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13681
13682     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13683
13684     PrintPGNTags(f, &gameInfo);
13685
13686     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13687
13688     if (backwardMostMove > 0 || startedFromSetupPosition) {
13689         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13690         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13691         fprintf(f, "\n{--------------\n");
13692         PrintPosition(f, backwardMostMove);
13693         fprintf(f, "--------------}\n");
13694         free(fen);
13695     }
13696     else {
13697         /* [AS] Out of book annotation */
13698         if( appData.saveOutOfBookInfo ) {
13699             char buf[64];
13700
13701             GetOutOfBookInfo( buf );
13702
13703             if( buf[0] != '\0' ) {
13704                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13705             }
13706         }
13707
13708         fprintf(f, "\n");
13709     }
13710
13711     i = backwardMostMove;
13712     linelen = 0;
13713     newblock = TRUE;
13714
13715     while (i < forwardMostMove) {
13716         /* Print comments preceding this move */
13717         if (commentList[i] != NULL) {
13718             if (linelen > 0) fprintf(f, "\n");
13719             fprintf(f, "%s", commentList[i]);
13720             linelen = 0;
13721             newblock = TRUE;
13722         }
13723
13724         /* Format move number */
13725         if ((i % 2) == 0)
13726           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13727         else
13728           if (newblock)
13729             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13730           else
13731             numtext[0] = NULLCHAR;
13732
13733         numlen = strlen(numtext);
13734         newblock = FALSE;
13735
13736         /* Print move number */
13737         blank = linelen > 0 && numlen > 0;
13738         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13739             fprintf(f, "\n");
13740             linelen = 0;
13741             blank = 0;
13742         }
13743         if (blank) {
13744             fprintf(f, " ");
13745             linelen++;
13746         }
13747         fprintf(f, "%s", numtext);
13748         linelen += numlen;
13749
13750         /* Get move */
13751         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13752         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13753
13754         /* Print move */
13755         blank = linelen > 0 && movelen > 0;
13756         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13757             fprintf(f, "\n");
13758             linelen = 0;
13759             blank = 0;
13760         }
13761         if (blank) {
13762             fprintf(f, " ");
13763             linelen++;
13764         }
13765         fprintf(f, "%s", move_buffer);
13766         linelen += movelen;
13767
13768         /* [AS] Add PV info if present */
13769         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13770             /* [HGM] add time */
13771             char buf[MSG_SIZ]; int seconds;
13772
13773             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13774
13775             if( seconds <= 0)
13776               buf[0] = 0;
13777             else
13778               if( seconds < 30 )
13779                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13780               else
13781                 {
13782                   seconds = (seconds + 4)/10; // round to full seconds
13783                   if( seconds < 60 )
13784                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13785                   else
13786                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13787                 }
13788
13789             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13790                       pvInfoList[i].score >= 0 ? "+" : "",
13791                       pvInfoList[i].score / 100.0,
13792                       pvInfoList[i].depth,
13793                       buf );
13794
13795             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13796
13797             /* Print score/depth */
13798             blank = linelen > 0 && movelen > 0;
13799             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13800                 fprintf(f, "\n");
13801                 linelen = 0;
13802                 blank = 0;
13803             }
13804             if (blank) {
13805                 fprintf(f, " ");
13806                 linelen++;
13807             }
13808             fprintf(f, "%s", move_buffer);
13809             linelen += movelen;
13810         }
13811
13812         i++;
13813     }
13814
13815     /* Start a new line */
13816     if (linelen > 0) fprintf(f, "\n");
13817
13818     /* Print comments after last move */
13819     if (commentList[i] != NULL) {
13820         fprintf(f, "%s\n", commentList[i]);
13821     }
13822
13823     /* Print result */
13824     if (gameInfo.resultDetails != NULL &&
13825         gameInfo.resultDetails[0] != NULLCHAR) {
13826         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13827         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13828            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13829             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13830         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13831     } else {
13832         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13833     }
13834 }
13835
13836 /* Save game in PGN style and close the file */
13837 int
13838 SaveGamePGN (FILE *f)
13839 {
13840     SaveGamePGN2(f);
13841     fclose(f);
13842     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13843     return TRUE;
13844 }
13845
13846 /* Save game in old style and close the file */
13847 int
13848 SaveGameOldStyle (FILE *f)
13849 {
13850     int i, offset;
13851     time_t tm;
13852
13853     tm = time((time_t *) NULL);
13854
13855     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13856     PrintOpponents(f);
13857
13858     if (backwardMostMove > 0 || startedFromSetupPosition) {
13859         fprintf(f, "\n[--------------\n");
13860         PrintPosition(f, backwardMostMove);
13861         fprintf(f, "--------------]\n");
13862     } else {
13863         fprintf(f, "\n");
13864     }
13865
13866     i = backwardMostMove;
13867     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13868
13869     while (i < forwardMostMove) {
13870         if (commentList[i] != NULL) {
13871             fprintf(f, "[%s]\n", commentList[i]);
13872         }
13873
13874         if ((i % 2) == 1) {
13875             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13876             i++;
13877         } else {
13878             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13879             i++;
13880             if (commentList[i] != NULL) {
13881                 fprintf(f, "\n");
13882                 continue;
13883             }
13884             if (i >= forwardMostMove) {
13885                 fprintf(f, "\n");
13886                 break;
13887             }
13888             fprintf(f, "%s\n", parseList[i]);
13889             i++;
13890         }
13891     }
13892
13893     if (commentList[i] != NULL) {
13894         fprintf(f, "[%s]\n", commentList[i]);
13895     }
13896
13897     /* This isn't really the old style, but it's close enough */
13898     if (gameInfo.resultDetails != NULL &&
13899         gameInfo.resultDetails[0] != NULLCHAR) {
13900         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13901                 gameInfo.resultDetails);
13902     } else {
13903         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13904     }
13905
13906     fclose(f);
13907     return TRUE;
13908 }
13909
13910 /* Save the current game to open file f and close the file */
13911 int
13912 SaveGame (FILE *f, int dummy, char *dummy2)
13913 {
13914     if (gameMode == EditPosition) EditPositionDone(TRUE);
13915     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13916     if (appData.oldSaveStyle)
13917       return SaveGameOldStyle(f);
13918     else
13919       return SaveGamePGN(f);
13920 }
13921
13922 /* Save the current position to the given file */
13923 int
13924 SavePositionToFile (char *filename)
13925 {
13926     FILE *f;
13927     char buf[MSG_SIZ];
13928
13929     if (strcmp(filename, "-") == 0) {
13930         return SavePosition(stdout, 0, NULL);
13931     } else {
13932         f = fopen(filename, "a");
13933         if (f == NULL) {
13934             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13935             DisplayError(buf, errno);
13936             return FALSE;
13937         } else {
13938             safeStrCpy(buf, lastMsg, MSG_SIZ);
13939             DisplayMessage(_("Waiting for access to save file"), "");
13940             flock(fileno(f), LOCK_EX); // [HGM] lock
13941             DisplayMessage(_("Saving position"), "");
13942             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13943             SavePosition(f, 0, NULL);
13944             DisplayMessage(buf, "");
13945             return TRUE;
13946         }
13947     }
13948 }
13949
13950 /* Save the current position to the given open file and close the file */
13951 int
13952 SavePosition (FILE *f, int dummy, char *dummy2)
13953 {
13954     time_t tm;
13955     char *fen;
13956
13957     if (gameMode == EditPosition) EditPositionDone(TRUE);
13958     if (appData.oldSaveStyle) {
13959         tm = time((time_t *) NULL);
13960
13961         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13962         PrintOpponents(f);
13963         fprintf(f, "[--------------\n");
13964         PrintPosition(f, currentMove);
13965         fprintf(f, "--------------]\n");
13966     } else {
13967         fen = PositionToFEN(currentMove, NULL, 1);
13968         fprintf(f, "%s\n", fen);
13969         free(fen);
13970     }
13971     fclose(f);
13972     return TRUE;
13973 }
13974
13975 void
13976 ReloadCmailMsgEvent (int unregister)
13977 {
13978 #if !WIN32
13979     static char *inFilename = NULL;
13980     static char *outFilename;
13981     int i;
13982     struct stat inbuf, outbuf;
13983     int status;
13984
13985     /* Any registered moves are unregistered if unregister is set, */
13986     /* i.e. invoked by the signal handler */
13987     if (unregister) {
13988         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13989             cmailMoveRegistered[i] = FALSE;
13990             if (cmailCommentList[i] != NULL) {
13991                 free(cmailCommentList[i]);
13992                 cmailCommentList[i] = NULL;
13993             }
13994         }
13995         nCmailMovesRegistered = 0;
13996     }
13997
13998     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13999         cmailResult[i] = CMAIL_NOT_RESULT;
14000     }
14001     nCmailResults = 0;
14002
14003     if (inFilename == NULL) {
14004         /* Because the filenames are static they only get malloced once  */
14005         /* and they never get freed                                      */
14006         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14007         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14008
14009         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14010         sprintf(outFilename, "%s.out", appData.cmailGameName);
14011     }
14012
14013     status = stat(outFilename, &outbuf);
14014     if (status < 0) {
14015         cmailMailedMove = FALSE;
14016     } else {
14017         status = stat(inFilename, &inbuf);
14018         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14019     }
14020
14021     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14022        counts the games, notes how each one terminated, etc.
14023
14024        It would be nice to remove this kludge and instead gather all
14025        the information while building the game list.  (And to keep it
14026        in the game list nodes instead of having a bunch of fixed-size
14027        parallel arrays.)  Note this will require getting each game's
14028        termination from the PGN tags, as the game list builder does
14029        not process the game moves.  --mann
14030        */
14031     cmailMsgLoaded = TRUE;
14032     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14033
14034     /* Load first game in the file or popup game menu */
14035     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14036
14037 #endif /* !WIN32 */
14038     return;
14039 }
14040
14041 int
14042 RegisterMove ()
14043 {
14044     FILE *f;
14045     char string[MSG_SIZ];
14046
14047     if (   cmailMailedMove
14048         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14049         return TRUE;            /* Allow free viewing  */
14050     }
14051
14052     /* Unregister move to ensure that we don't leave RegisterMove        */
14053     /* with the move registered when the conditions for registering no   */
14054     /* longer hold                                                       */
14055     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14056         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14057         nCmailMovesRegistered --;
14058
14059         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14060           {
14061               free(cmailCommentList[lastLoadGameNumber - 1]);
14062               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14063           }
14064     }
14065
14066     if (cmailOldMove == -1) {
14067         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14068         return FALSE;
14069     }
14070
14071     if (currentMove > cmailOldMove + 1) {
14072         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14073         return FALSE;
14074     }
14075
14076     if (currentMove < cmailOldMove) {
14077         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14078         return FALSE;
14079     }
14080
14081     if (forwardMostMove > currentMove) {
14082         /* Silently truncate extra moves */
14083         TruncateGame();
14084     }
14085
14086     if (   (currentMove == cmailOldMove + 1)
14087         || (   (currentMove == cmailOldMove)
14088             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14089                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14090         if (gameInfo.result != GameUnfinished) {
14091             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14092         }
14093
14094         if (commentList[currentMove] != NULL) {
14095             cmailCommentList[lastLoadGameNumber - 1]
14096               = StrSave(commentList[currentMove]);
14097         }
14098         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14099
14100         if (appData.debugMode)
14101           fprintf(debugFP, "Saving %s for game %d\n",
14102                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14103
14104         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14105
14106         f = fopen(string, "w");
14107         if (appData.oldSaveStyle) {
14108             SaveGameOldStyle(f); /* also closes the file */
14109
14110             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14111             f = fopen(string, "w");
14112             SavePosition(f, 0, NULL); /* also closes the file */
14113         } else {
14114             fprintf(f, "{--------------\n");
14115             PrintPosition(f, currentMove);
14116             fprintf(f, "--------------}\n\n");
14117
14118             SaveGame(f, 0, NULL); /* also closes the file*/
14119         }
14120
14121         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14122         nCmailMovesRegistered ++;
14123     } else if (nCmailGames == 1) {
14124         DisplayError(_("You have not made a move yet"), 0);
14125         return FALSE;
14126     }
14127
14128     return TRUE;
14129 }
14130
14131 void
14132 MailMoveEvent ()
14133 {
14134 #if !WIN32
14135     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14136     FILE *commandOutput;
14137     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14138     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14139     int nBuffers;
14140     int i;
14141     int archived;
14142     char *arcDir;
14143
14144     if (! cmailMsgLoaded) {
14145         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14146         return;
14147     }
14148
14149     if (nCmailGames == nCmailResults) {
14150         DisplayError(_("No unfinished games"), 0);
14151         return;
14152     }
14153
14154 #if CMAIL_PROHIBIT_REMAIL
14155     if (cmailMailedMove) {
14156       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);
14157         DisplayError(msg, 0);
14158         return;
14159     }
14160 #endif
14161
14162     if (! (cmailMailedMove || RegisterMove())) return;
14163
14164     if (   cmailMailedMove
14165         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14166       snprintf(string, MSG_SIZ, partCommandString,
14167                appData.debugMode ? " -v" : "", appData.cmailGameName);
14168         commandOutput = popen(string, "r");
14169
14170         if (commandOutput == NULL) {
14171             DisplayError(_("Failed to invoke cmail"), 0);
14172         } else {
14173             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14174                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14175             }
14176             if (nBuffers > 1) {
14177                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14178                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14179                 nBytes = MSG_SIZ - 1;
14180             } else {
14181                 (void) memcpy(msg, buffer, nBytes);
14182             }
14183             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14184
14185             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14186                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14187
14188                 archived = TRUE;
14189                 for (i = 0; i < nCmailGames; i ++) {
14190                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14191                         archived = FALSE;
14192                     }
14193                 }
14194                 if (   archived
14195                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14196                         != NULL)) {
14197                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14198                            arcDir,
14199                            appData.cmailGameName,
14200                            gameInfo.date);
14201                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14202                     cmailMsgLoaded = FALSE;
14203                 }
14204             }
14205
14206             DisplayInformation(msg);
14207             pclose(commandOutput);
14208         }
14209     } else {
14210         if ((*cmailMsg) != '\0') {
14211             DisplayInformation(cmailMsg);
14212         }
14213     }
14214
14215     return;
14216 #endif /* !WIN32 */
14217 }
14218
14219 char *
14220 CmailMsg ()
14221 {
14222 #if WIN32
14223     return NULL;
14224 #else
14225     int  prependComma = 0;
14226     char number[5];
14227     char string[MSG_SIZ];       /* Space for game-list */
14228     int  i;
14229
14230     if (!cmailMsgLoaded) return "";
14231
14232     if (cmailMailedMove) {
14233       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14234     } else {
14235         /* Create a list of games left */
14236       snprintf(string, MSG_SIZ, "[");
14237         for (i = 0; i < nCmailGames; i ++) {
14238             if (! (   cmailMoveRegistered[i]
14239                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14240                 if (prependComma) {
14241                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14242                 } else {
14243                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14244                     prependComma = 1;
14245                 }
14246
14247                 strcat(string, number);
14248             }
14249         }
14250         strcat(string, "]");
14251
14252         if (nCmailMovesRegistered + nCmailResults == 0) {
14253             switch (nCmailGames) {
14254               case 1:
14255                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14256                 break;
14257
14258               case 2:
14259                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14260                 break;
14261
14262               default:
14263                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14264                          nCmailGames);
14265                 break;
14266             }
14267         } else {
14268             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14269               case 1:
14270                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14271                          string);
14272                 break;
14273
14274               case 0:
14275                 if (nCmailResults == nCmailGames) {
14276                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14277                 } else {
14278                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14279                 }
14280                 break;
14281
14282               default:
14283                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14284                          string);
14285             }
14286         }
14287     }
14288     return cmailMsg;
14289 #endif /* WIN32 */
14290 }
14291
14292 void
14293 ResetGameEvent ()
14294 {
14295     if (gameMode == Training)
14296       SetTrainingModeOff();
14297
14298     Reset(TRUE, TRUE);
14299     cmailMsgLoaded = FALSE;
14300     if (appData.icsActive) {
14301       SendToICS(ics_prefix);
14302       SendToICS("refresh\n");
14303     }
14304 }
14305
14306 void
14307 ExitEvent (int status)
14308 {
14309     exiting++;
14310     if (exiting > 2) {
14311       /* Give up on clean exit */
14312       exit(status);
14313     }
14314     if (exiting > 1) {
14315       /* Keep trying for clean exit */
14316       return;
14317     }
14318
14319     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14320     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14321
14322     if (telnetISR != NULL) {
14323       RemoveInputSource(telnetISR);
14324     }
14325     if (icsPR != NoProc) {
14326       DestroyChildProcess(icsPR, TRUE);
14327     }
14328
14329     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14330     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14331
14332     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14333     /* make sure this other one finishes before killing it!                  */
14334     if(endingGame) { int count = 0;
14335         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14336         while(endingGame && count++ < 10) DoSleep(1);
14337         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14338     }
14339
14340     /* Kill off chess programs */
14341     if (first.pr != NoProc) {
14342         ExitAnalyzeMode();
14343
14344         DoSleep( appData.delayBeforeQuit );
14345         SendToProgram("quit\n", &first);
14346         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14347     }
14348     if (second.pr != NoProc) {
14349         DoSleep( appData.delayBeforeQuit );
14350         SendToProgram("quit\n", &second);
14351         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14352     }
14353     if (first.isr != NULL) {
14354         RemoveInputSource(first.isr);
14355     }
14356     if (second.isr != NULL) {
14357         RemoveInputSource(second.isr);
14358     }
14359
14360     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14361     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14362
14363     ShutDownFrontEnd();
14364     exit(status);
14365 }
14366
14367 void
14368 PauseEngine (ChessProgramState *cps)
14369 {
14370     SendToProgram("pause\n", cps);
14371     cps->pause = 2;
14372 }
14373
14374 void
14375 UnPauseEngine (ChessProgramState *cps)
14376 {
14377     SendToProgram("resume\n", cps);
14378     cps->pause = 1;
14379 }
14380
14381 void
14382 PauseEvent ()
14383 {
14384     if (appData.debugMode)
14385         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14386     if (pausing) {
14387         pausing = FALSE;
14388         ModeHighlight();
14389         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14390             StartClocks();
14391             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14392                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14393                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14394             }
14395             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14396             HandleMachineMove(stashedInputMove, stalledEngine);
14397             stalledEngine = NULL;
14398             return;
14399         }
14400         if (gameMode == MachinePlaysWhite ||
14401             gameMode == TwoMachinesPlay   ||
14402             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14403             if(first.pause)  UnPauseEngine(&first);
14404             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14405             if(second.pause) UnPauseEngine(&second);
14406             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14407             StartClocks();
14408         } else {
14409             DisplayBothClocks();
14410         }
14411         if (gameMode == PlayFromGameFile) {
14412             if (appData.timeDelay >= 0)
14413                 AutoPlayGameLoop();
14414         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14415             Reset(FALSE, TRUE);
14416             SendToICS(ics_prefix);
14417             SendToICS("refresh\n");
14418         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14419             ForwardInner(forwardMostMove);
14420         }
14421         pauseExamInvalid = FALSE;
14422     } else {
14423         switch (gameMode) {
14424           default:
14425             return;
14426           case IcsExamining:
14427             pauseExamForwardMostMove = forwardMostMove;
14428             pauseExamInvalid = FALSE;
14429             /* fall through */
14430           case IcsObserving:
14431           case IcsPlayingWhite:
14432           case IcsPlayingBlack:
14433             pausing = TRUE;
14434             ModeHighlight();
14435             return;
14436           case PlayFromGameFile:
14437             (void) StopLoadGameTimer();
14438             pausing = TRUE;
14439             ModeHighlight();
14440             break;
14441           case BeginningOfGame:
14442             if (appData.icsActive) return;
14443             /* else fall through */
14444           case MachinePlaysWhite:
14445           case MachinePlaysBlack:
14446           case TwoMachinesPlay:
14447             if (forwardMostMove == 0)
14448               return;           /* don't pause if no one has moved */
14449             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14450                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14451                 if(onMove->pause) {           // thinking engine can be paused
14452                     PauseEngine(onMove);      // do it
14453                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14454                         PauseEngine(onMove->other);
14455                     else
14456                         SendToProgram("easy\n", onMove->other);
14457                     StopClocks();
14458                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14459             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14460                 if(first.pause) {
14461                     PauseEngine(&first);
14462                     StopClocks();
14463                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14464             } else { // human on move, pause pondering by either method
14465                 if(first.pause)
14466                     PauseEngine(&first);
14467                 else if(appData.ponderNextMove)
14468                     SendToProgram("easy\n", &first);
14469                 StopClocks();
14470             }
14471             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14472           case AnalyzeMode:
14473             pausing = TRUE;
14474             ModeHighlight();
14475             break;
14476         }
14477     }
14478 }
14479
14480 void
14481 EditCommentEvent ()
14482 {
14483     char title[MSG_SIZ];
14484
14485     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14486       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14487     } else {
14488       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14489                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14490                parseList[currentMove - 1]);
14491     }
14492
14493     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14494 }
14495
14496
14497 void
14498 EditTagsEvent ()
14499 {
14500     char *tags = PGNTags(&gameInfo);
14501     bookUp = FALSE;
14502     EditTagsPopUp(tags, NULL);
14503     free(tags);
14504 }
14505
14506 void
14507 ToggleSecond ()
14508 {
14509   if(second.analyzing) {
14510     SendToProgram("exit\n", &second);
14511     second.analyzing = FALSE;
14512   } else {
14513     if (second.pr == NoProc) StartChessProgram(&second);
14514     InitChessProgram(&second, FALSE);
14515     FeedMovesToProgram(&second, currentMove);
14516
14517     SendToProgram("analyze\n", &second);
14518     second.analyzing = TRUE;
14519   }
14520 }
14521
14522 /* Toggle ShowThinking */
14523 void
14524 ToggleShowThinking()
14525 {
14526   appData.showThinking = !appData.showThinking;
14527   ShowThinkingEvent();
14528 }
14529
14530 int
14531 AnalyzeModeEvent ()
14532 {
14533     char buf[MSG_SIZ];
14534
14535     if (!first.analysisSupport) {
14536       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14537       DisplayError(buf, 0);
14538       return 0;
14539     }
14540     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14541     if (appData.icsActive) {
14542         if (gameMode != IcsObserving) {
14543           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14544             DisplayError(buf, 0);
14545             /* secure check */
14546             if (appData.icsEngineAnalyze) {
14547                 if (appData.debugMode)
14548                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14549                 ExitAnalyzeMode();
14550                 ModeHighlight();
14551             }
14552             return 0;
14553         }
14554         /* if enable, user wants to disable icsEngineAnalyze */
14555         if (appData.icsEngineAnalyze) {
14556                 ExitAnalyzeMode();
14557                 ModeHighlight();
14558                 return 0;
14559         }
14560         appData.icsEngineAnalyze = TRUE;
14561         if (appData.debugMode)
14562             fprintf(debugFP, "ICS engine analyze starting... \n");
14563     }
14564
14565     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14566     if (appData.noChessProgram || gameMode == AnalyzeMode)
14567       return 0;
14568
14569     if (gameMode != AnalyzeFile) {
14570         if (!appData.icsEngineAnalyze) {
14571                EditGameEvent();
14572                if (gameMode != EditGame) return 0;
14573         }
14574         if (!appData.showThinking) ToggleShowThinking();
14575         ResurrectChessProgram();
14576         SendToProgram("analyze\n", &first);
14577         first.analyzing = TRUE;
14578         /*first.maybeThinking = TRUE;*/
14579         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14580         EngineOutputPopUp();
14581     }
14582     if (!appData.icsEngineAnalyze) {
14583         gameMode = AnalyzeMode;
14584         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14585     }
14586     pausing = FALSE;
14587     ModeHighlight();
14588     SetGameInfo();
14589
14590     StartAnalysisClock();
14591     GetTimeMark(&lastNodeCountTime);
14592     lastNodeCount = 0;
14593     return 1;
14594 }
14595
14596 void
14597 AnalyzeFileEvent ()
14598 {
14599     if (appData.noChessProgram || gameMode == AnalyzeFile)
14600       return;
14601
14602     if (!first.analysisSupport) {
14603       char buf[MSG_SIZ];
14604       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14605       DisplayError(buf, 0);
14606       return;
14607     }
14608
14609     if (gameMode != AnalyzeMode) {
14610         keepInfo = 1; // mere annotating should not alter PGN tags
14611         EditGameEvent();
14612         keepInfo = 0;
14613         if (gameMode != EditGame) return;
14614         if (!appData.showThinking) ToggleShowThinking();
14615         ResurrectChessProgram();
14616         SendToProgram("analyze\n", &first);
14617         first.analyzing = TRUE;
14618         /*first.maybeThinking = TRUE;*/
14619         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14620         EngineOutputPopUp();
14621     }
14622     gameMode = AnalyzeFile;
14623     pausing = FALSE;
14624     ModeHighlight();
14625
14626     StartAnalysisClock();
14627     GetTimeMark(&lastNodeCountTime);
14628     lastNodeCount = 0;
14629     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14630     AnalysisPeriodicEvent(1);
14631 }
14632
14633 void
14634 MachineWhiteEvent ()
14635 {
14636     char buf[MSG_SIZ];
14637     char *bookHit = NULL;
14638
14639     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14640       return;
14641
14642
14643     if (gameMode == PlayFromGameFile ||
14644         gameMode == TwoMachinesPlay  ||
14645         gameMode == Training         ||
14646         gameMode == AnalyzeMode      ||
14647         gameMode == EndOfGame)
14648         EditGameEvent();
14649
14650     if (gameMode == EditPosition)
14651         EditPositionDone(TRUE);
14652
14653     if (!WhiteOnMove(currentMove)) {
14654         DisplayError(_("It is not White's turn"), 0);
14655         return;
14656     }
14657
14658     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14659       ExitAnalyzeMode();
14660
14661     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14662         gameMode == AnalyzeFile)
14663         TruncateGame();
14664
14665     ResurrectChessProgram();    /* in case it isn't running */
14666     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14667         gameMode = MachinePlaysWhite;
14668         ResetClocks();
14669     } else
14670     gameMode = MachinePlaysWhite;
14671     pausing = FALSE;
14672     ModeHighlight();
14673     SetGameInfo();
14674     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14675     DisplayTitle(buf);
14676     if (first.sendName) {
14677       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14678       SendToProgram(buf, &first);
14679     }
14680     if (first.sendTime) {
14681       if (first.useColors) {
14682         SendToProgram("black\n", &first); /*gnu kludge*/
14683       }
14684       SendTimeRemaining(&first, TRUE);
14685     }
14686     if (first.useColors) {
14687       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14688     }
14689     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14690     SetMachineThinkingEnables();
14691     first.maybeThinking = TRUE;
14692     StartClocks();
14693     firstMove = FALSE;
14694
14695     if (appData.autoFlipView && !flipView) {
14696       flipView = !flipView;
14697       DrawPosition(FALSE, NULL);
14698       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14699     }
14700
14701     if(bookHit) { // [HGM] book: simulate book reply
14702         static char bookMove[MSG_SIZ]; // a bit generous?
14703
14704         programStats.nodes = programStats.depth = programStats.time =
14705         programStats.score = programStats.got_only_move = 0;
14706         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14707
14708         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14709         strcat(bookMove, bookHit);
14710         HandleMachineMove(bookMove, &first);
14711     }
14712 }
14713
14714 void
14715 MachineBlackEvent ()
14716 {
14717   char buf[MSG_SIZ];
14718   char *bookHit = NULL;
14719
14720     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14721         return;
14722
14723
14724     if (gameMode == PlayFromGameFile ||
14725         gameMode == TwoMachinesPlay  ||
14726         gameMode == Training         ||
14727         gameMode == AnalyzeMode      ||
14728         gameMode == EndOfGame)
14729         EditGameEvent();
14730
14731     if (gameMode == EditPosition)
14732         EditPositionDone(TRUE);
14733
14734     if (WhiteOnMove(currentMove)) {
14735         DisplayError(_("It is not Black's turn"), 0);
14736         return;
14737     }
14738
14739     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14740       ExitAnalyzeMode();
14741
14742     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14743         gameMode == AnalyzeFile)
14744         TruncateGame();
14745
14746     ResurrectChessProgram();    /* in case it isn't running */
14747     gameMode = MachinePlaysBlack;
14748     pausing = FALSE;
14749     ModeHighlight();
14750     SetGameInfo();
14751     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14752     DisplayTitle(buf);
14753     if (first.sendName) {
14754       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14755       SendToProgram(buf, &first);
14756     }
14757     if (first.sendTime) {
14758       if (first.useColors) {
14759         SendToProgram("white\n", &first); /*gnu kludge*/
14760       }
14761       SendTimeRemaining(&first, FALSE);
14762     }
14763     if (first.useColors) {
14764       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14765     }
14766     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14767     SetMachineThinkingEnables();
14768     first.maybeThinking = TRUE;
14769     StartClocks();
14770
14771     if (appData.autoFlipView && flipView) {
14772       flipView = !flipView;
14773       DrawPosition(FALSE, NULL);
14774       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14775     }
14776     if(bookHit) { // [HGM] book: simulate book reply
14777         static char bookMove[MSG_SIZ]; // a bit generous?
14778
14779         programStats.nodes = programStats.depth = programStats.time =
14780         programStats.score = programStats.got_only_move = 0;
14781         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14782
14783         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14784         strcat(bookMove, bookHit);
14785         HandleMachineMove(bookMove, &first);
14786     }
14787 }
14788
14789
14790 void
14791 DisplayTwoMachinesTitle ()
14792 {
14793     char buf[MSG_SIZ];
14794     if (appData.matchGames > 0) {
14795         if(appData.tourneyFile[0]) {
14796           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14797                    gameInfo.white, _("vs."), gameInfo.black,
14798                    nextGame+1, appData.matchGames+1,
14799                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14800         } else
14801         if (first.twoMachinesColor[0] == 'w') {
14802           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14803                    gameInfo.white, _("vs."),  gameInfo.black,
14804                    first.matchWins, second.matchWins,
14805                    matchGame - 1 - (first.matchWins + second.matchWins));
14806         } else {
14807           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14808                    gameInfo.white, _("vs."), gameInfo.black,
14809                    second.matchWins, first.matchWins,
14810                    matchGame - 1 - (first.matchWins + second.matchWins));
14811         }
14812     } else {
14813       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14814     }
14815     DisplayTitle(buf);
14816 }
14817
14818 void
14819 SettingsMenuIfReady ()
14820 {
14821   if (second.lastPing != second.lastPong) {
14822     DisplayMessage("", _("Waiting for second chess program"));
14823     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14824     return;
14825   }
14826   ThawUI();
14827   DisplayMessage("", "");
14828   SettingsPopUp(&second);
14829 }
14830
14831 int
14832 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14833 {
14834     char buf[MSG_SIZ];
14835     if (cps->pr == NoProc) {
14836         StartChessProgram(cps);
14837         if (cps->protocolVersion == 1) {
14838           retry();
14839           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14840         } else {
14841           /* kludge: allow timeout for initial "feature" command */
14842           if(retry != TwoMachinesEventIfReady) FreezeUI();
14843           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14844           DisplayMessage("", buf);
14845           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14846         }
14847         return 1;
14848     }
14849     return 0;
14850 }
14851
14852 void
14853 TwoMachinesEvent P((void))
14854 {
14855     int i;
14856     char buf[MSG_SIZ];
14857     ChessProgramState *onmove;
14858     char *bookHit = NULL;
14859     static int stalling = 0;
14860     TimeMark now;
14861     long wait;
14862
14863     if (appData.noChessProgram) return;
14864
14865     switch (gameMode) {
14866       case TwoMachinesPlay:
14867         return;
14868       case MachinePlaysWhite:
14869       case MachinePlaysBlack:
14870         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14871             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14872             return;
14873         }
14874         /* fall through */
14875       case BeginningOfGame:
14876       case PlayFromGameFile:
14877       case EndOfGame:
14878         EditGameEvent();
14879         if (gameMode != EditGame) return;
14880         break;
14881       case EditPosition:
14882         EditPositionDone(TRUE);
14883         break;
14884       case AnalyzeMode:
14885       case AnalyzeFile:
14886         ExitAnalyzeMode();
14887         break;
14888       case EditGame:
14889       default:
14890         break;
14891     }
14892
14893 //    forwardMostMove = currentMove;
14894     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14895     startingEngine = TRUE;
14896
14897     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14898
14899     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14900     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14901       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14902       return;
14903     }
14904     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14905
14906     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14907                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14908         startingEngine = matchMode = FALSE;
14909         DisplayError("second engine does not play this", 0);
14910         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14911         EditGameEvent(); // switch back to EditGame mode
14912         return;
14913     }
14914
14915     if(!stalling) {
14916       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14917       SendToProgram("force\n", &second);
14918       stalling = 1;
14919       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14920       return;
14921     }
14922     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14923     if(appData.matchPause>10000 || appData.matchPause<10)
14924                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14925     wait = SubtractTimeMarks(&now, &pauseStart);
14926     if(wait < appData.matchPause) {
14927         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14928         return;
14929     }
14930     // we are now committed to starting the game
14931     stalling = 0;
14932     DisplayMessage("", "");
14933     if (startedFromSetupPosition) {
14934         SendBoard(&second, backwardMostMove);
14935     if (appData.debugMode) {
14936         fprintf(debugFP, "Two Machines\n");
14937     }
14938     }
14939     for (i = backwardMostMove; i < forwardMostMove; i++) {
14940         SendMoveToProgram(i, &second);
14941     }
14942
14943     gameMode = TwoMachinesPlay;
14944     pausing = startingEngine = FALSE;
14945     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14946     SetGameInfo();
14947     DisplayTwoMachinesTitle();
14948     firstMove = TRUE;
14949     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14950         onmove = &first;
14951     } else {
14952         onmove = &second;
14953     }
14954     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14955     SendToProgram(first.computerString, &first);
14956     if (first.sendName) {
14957       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14958       SendToProgram(buf, &first);
14959     }
14960     SendToProgram(second.computerString, &second);
14961     if (second.sendName) {
14962       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14963       SendToProgram(buf, &second);
14964     }
14965
14966     ResetClocks();
14967     if (!first.sendTime || !second.sendTime) {
14968         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14969         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14970     }
14971     if (onmove->sendTime) {
14972       if (onmove->useColors) {
14973         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14974       }
14975       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14976     }
14977     if (onmove->useColors) {
14978       SendToProgram(onmove->twoMachinesColor, onmove);
14979     }
14980     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14981 //    SendToProgram("go\n", onmove);
14982     onmove->maybeThinking = TRUE;
14983     SetMachineThinkingEnables();
14984
14985     StartClocks();
14986
14987     if(bookHit) { // [HGM] book: simulate book reply
14988         static char bookMove[MSG_SIZ]; // a bit generous?
14989
14990         programStats.nodes = programStats.depth = programStats.time =
14991         programStats.score = programStats.got_only_move = 0;
14992         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14993
14994         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14995         strcat(bookMove, bookHit);
14996         savedMessage = bookMove; // args for deferred call
14997         savedState = onmove;
14998         ScheduleDelayedEvent(DeferredBookMove, 1);
14999     }
15000 }
15001
15002 void
15003 TrainingEvent ()
15004 {
15005     if (gameMode == Training) {
15006       SetTrainingModeOff();
15007       gameMode = PlayFromGameFile;
15008       DisplayMessage("", _("Training mode off"));
15009     } else {
15010       gameMode = Training;
15011       animateTraining = appData.animate;
15012
15013       /* make sure we are not already at the end of the game */
15014       if (currentMove < forwardMostMove) {
15015         SetTrainingModeOn();
15016         DisplayMessage("", _("Training mode on"));
15017       } else {
15018         gameMode = PlayFromGameFile;
15019         DisplayError(_("Already at end of game"), 0);
15020       }
15021     }
15022     ModeHighlight();
15023 }
15024
15025 void
15026 IcsClientEvent ()
15027 {
15028     if (!appData.icsActive) return;
15029     switch (gameMode) {
15030       case IcsPlayingWhite:
15031       case IcsPlayingBlack:
15032       case IcsObserving:
15033       case IcsIdle:
15034       case BeginningOfGame:
15035       case IcsExamining:
15036         return;
15037
15038       case EditGame:
15039         break;
15040
15041       case EditPosition:
15042         EditPositionDone(TRUE);
15043         break;
15044
15045       case AnalyzeMode:
15046       case AnalyzeFile:
15047         ExitAnalyzeMode();
15048         break;
15049
15050       default:
15051         EditGameEvent();
15052         break;
15053     }
15054
15055     gameMode = IcsIdle;
15056     ModeHighlight();
15057     return;
15058 }
15059
15060 void
15061 EditGameEvent ()
15062 {
15063     int i;
15064
15065     switch (gameMode) {
15066       case Training:
15067         SetTrainingModeOff();
15068         break;
15069       case MachinePlaysWhite:
15070       case MachinePlaysBlack:
15071       case BeginningOfGame:
15072         SendToProgram("force\n", &first);
15073         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15074             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15075                 char buf[MSG_SIZ];
15076                 abortEngineThink = TRUE;
15077                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15078                 SendToProgram(buf, &first);
15079                 DisplayMessage("Aborting engine think", "");
15080                 FreezeUI();
15081             }
15082         }
15083         SetUserThinkingEnables();
15084         break;
15085       case PlayFromGameFile:
15086         (void) StopLoadGameTimer();
15087         if (gameFileFP != NULL) {
15088             gameFileFP = NULL;
15089         }
15090         break;
15091       case EditPosition:
15092         EditPositionDone(TRUE);
15093         break;
15094       case AnalyzeMode:
15095       case AnalyzeFile:
15096         ExitAnalyzeMode();
15097         SendToProgram("force\n", &first);
15098         break;
15099       case TwoMachinesPlay:
15100         GameEnds(EndOfFile, NULL, GE_PLAYER);
15101         ResurrectChessProgram();
15102         SetUserThinkingEnables();
15103         break;
15104       case EndOfGame:
15105         ResurrectChessProgram();
15106         break;
15107       case IcsPlayingBlack:
15108       case IcsPlayingWhite:
15109         DisplayError(_("Warning: You are still playing a game"), 0);
15110         break;
15111       case IcsObserving:
15112         DisplayError(_("Warning: You are still observing a game"), 0);
15113         break;
15114       case IcsExamining:
15115         DisplayError(_("Warning: You are still examining a game"), 0);
15116         break;
15117       case IcsIdle:
15118         break;
15119       case EditGame:
15120       default:
15121         return;
15122     }
15123
15124     pausing = FALSE;
15125     StopClocks();
15126     first.offeredDraw = second.offeredDraw = 0;
15127
15128     if (gameMode == PlayFromGameFile) {
15129         whiteTimeRemaining = timeRemaining[0][currentMove];
15130         blackTimeRemaining = timeRemaining[1][currentMove];
15131         DisplayTitle("");
15132     }
15133
15134     if (gameMode == MachinePlaysWhite ||
15135         gameMode == MachinePlaysBlack ||
15136         gameMode == TwoMachinesPlay ||
15137         gameMode == EndOfGame) {
15138         i = forwardMostMove;
15139         while (i > currentMove) {
15140             SendToProgram("undo\n", &first);
15141             i--;
15142         }
15143         if(!adjustedClock) {
15144         whiteTimeRemaining = timeRemaining[0][currentMove];
15145         blackTimeRemaining = timeRemaining[1][currentMove];
15146         DisplayBothClocks();
15147         }
15148         if (whiteFlag || blackFlag) {
15149             whiteFlag = blackFlag = 0;
15150         }
15151         DisplayTitle("");
15152     }
15153
15154     gameMode = EditGame;
15155     ModeHighlight();
15156     SetGameInfo();
15157 }
15158
15159
15160 void
15161 EditPositionEvent ()
15162 {
15163     if (gameMode == EditPosition) {
15164         EditGameEvent();
15165         return;
15166     }
15167
15168     EditGameEvent();
15169     if (gameMode != EditGame) return;
15170
15171     gameMode = EditPosition;
15172     ModeHighlight();
15173     SetGameInfo();
15174     if (currentMove > 0)
15175       CopyBoard(boards[0], boards[currentMove]);
15176
15177     blackPlaysFirst = !WhiteOnMove(currentMove);
15178     ResetClocks();
15179     currentMove = forwardMostMove = backwardMostMove = 0;
15180     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15181     DisplayMove(-1);
15182     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15183 }
15184
15185 void
15186 ExitAnalyzeMode ()
15187 {
15188     /* [DM] icsEngineAnalyze - possible call from other functions */
15189     if (appData.icsEngineAnalyze) {
15190         appData.icsEngineAnalyze = FALSE;
15191
15192         DisplayMessage("",_("Close ICS engine analyze..."));
15193     }
15194     if (first.analysisSupport && first.analyzing) {
15195       SendToBoth("exit\n");
15196       first.analyzing = second.analyzing = FALSE;
15197     }
15198     thinkOutput[0] = NULLCHAR;
15199 }
15200
15201 void
15202 EditPositionDone (Boolean fakeRights)
15203 {
15204     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15205
15206     startedFromSetupPosition = TRUE;
15207     InitChessProgram(&first, FALSE);
15208     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15209       boards[0][EP_STATUS] = EP_NONE;
15210       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15211       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15212         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15213         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15214       } else boards[0][CASTLING][2] = NoRights;
15215       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15216         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15217         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15218       } else boards[0][CASTLING][5] = NoRights;
15219       if(gameInfo.variant == VariantSChess) {
15220         int i;
15221         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15222           boards[0][VIRGIN][i] = 0;
15223           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15224           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15225         }
15226       }
15227     }
15228     SendToProgram("force\n", &first);
15229     if (blackPlaysFirst) {
15230         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15231         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15232         currentMove = forwardMostMove = backwardMostMove = 1;
15233         CopyBoard(boards[1], boards[0]);
15234     } else {
15235         currentMove = forwardMostMove = backwardMostMove = 0;
15236     }
15237     SendBoard(&first, forwardMostMove);
15238     if (appData.debugMode) {
15239         fprintf(debugFP, "EditPosDone\n");
15240     }
15241     DisplayTitle("");
15242     DisplayMessage("", "");
15243     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15244     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15245     gameMode = EditGame;
15246     ModeHighlight();
15247     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15248     ClearHighlights(); /* [AS] */
15249 }
15250
15251 /* Pause for `ms' milliseconds */
15252 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15253 void
15254 TimeDelay (long ms)
15255 {
15256     TimeMark m1, m2;
15257
15258     GetTimeMark(&m1);
15259     do {
15260         GetTimeMark(&m2);
15261     } while (SubtractTimeMarks(&m2, &m1) < ms);
15262 }
15263
15264 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15265 void
15266 SendMultiLineToICS (char *buf)
15267 {
15268     char temp[MSG_SIZ+1], *p;
15269     int len;
15270
15271     len = strlen(buf);
15272     if (len > MSG_SIZ)
15273       len = MSG_SIZ;
15274
15275     strncpy(temp, buf, len);
15276     temp[len] = 0;
15277
15278     p = temp;
15279     while (*p) {
15280         if (*p == '\n' || *p == '\r')
15281           *p = ' ';
15282         ++p;
15283     }
15284
15285     strcat(temp, "\n");
15286     SendToICS(temp);
15287     SendToPlayer(temp, strlen(temp));
15288 }
15289
15290 void
15291 SetWhiteToPlayEvent ()
15292 {
15293     if (gameMode == EditPosition) {
15294         blackPlaysFirst = FALSE;
15295         DisplayBothClocks();    /* works because currentMove is 0 */
15296     } else if (gameMode == IcsExamining) {
15297         SendToICS(ics_prefix);
15298         SendToICS("tomove white\n");
15299     }
15300 }
15301
15302 void
15303 SetBlackToPlayEvent ()
15304 {
15305     if (gameMode == EditPosition) {
15306         blackPlaysFirst = TRUE;
15307         currentMove = 1;        /* kludge */
15308         DisplayBothClocks();
15309         currentMove = 0;
15310     } else if (gameMode == IcsExamining) {
15311         SendToICS(ics_prefix);
15312         SendToICS("tomove black\n");
15313     }
15314 }
15315
15316 void
15317 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15318 {
15319     char buf[MSG_SIZ];
15320     ChessSquare piece = boards[0][y][x];
15321     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15322     static int lastVariant;
15323
15324     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15325
15326     switch (selection) {
15327       case ClearBoard:
15328         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15329         MarkTargetSquares(1);
15330         CopyBoard(currentBoard, boards[0]);
15331         CopyBoard(menuBoard, initialPosition);
15332         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15333             SendToICS(ics_prefix);
15334             SendToICS("bsetup clear\n");
15335         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15336             SendToICS(ics_prefix);
15337             SendToICS("clearboard\n");
15338         } else {
15339             int nonEmpty = 0;
15340             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15341                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15342                 for (y = 0; y < BOARD_HEIGHT; y++) {
15343                     if (gameMode == IcsExamining) {
15344                         if (boards[currentMove][y][x] != EmptySquare) {
15345                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15346                                     AAA + x, ONE + y);
15347                             SendToICS(buf);
15348                         }
15349                     } else if(boards[0][y][x] != DarkSquare) {
15350                         if(boards[0][y][x] != p) nonEmpty++;
15351                         boards[0][y][x] = p;
15352                     }
15353                 }
15354             }
15355             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15356                 int r;
15357                 for(r = 0; r < BOARD_HEIGHT; r++) {
15358                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15359                     ChessSquare p = menuBoard[r][x];
15360                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15361                   }
15362                 }
15363                 DisplayMessage("Clicking clock again restores position", "");
15364                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15365                 if(!nonEmpty) { // asked to clear an empty board
15366                     CopyBoard(boards[0], menuBoard);
15367                 } else
15368                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15369                     CopyBoard(boards[0], initialPosition);
15370                 } else
15371                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15372                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15373                     CopyBoard(boards[0], erasedBoard);
15374                 } else
15375                     CopyBoard(erasedBoard, currentBoard);
15376
15377             }
15378         }
15379         if (gameMode == EditPosition) {
15380             DrawPosition(FALSE, boards[0]);
15381         }
15382         break;
15383
15384       case WhitePlay:
15385         SetWhiteToPlayEvent();
15386         break;
15387
15388       case BlackPlay:
15389         SetBlackToPlayEvent();
15390         break;
15391
15392       case EmptySquare:
15393         if (gameMode == IcsExamining) {
15394             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15395             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15396             SendToICS(buf);
15397         } else {
15398             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15399                 if(x == BOARD_LEFT-2) {
15400                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15401                     boards[0][y][1] = 0;
15402                 } else
15403                 if(x == BOARD_RGHT+1) {
15404                     if(y >= gameInfo.holdingsSize) break;
15405                     boards[0][y][BOARD_WIDTH-2] = 0;
15406                 } else break;
15407             }
15408             boards[0][y][x] = EmptySquare;
15409             DrawPosition(FALSE, boards[0]);
15410         }
15411         break;
15412
15413       case PromotePiece:
15414         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15415            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15416             selection = (ChessSquare) (PROMOTED(piece));
15417         } else if(piece == EmptySquare) selection = WhiteSilver;
15418         else selection = (ChessSquare)((int)piece - 1);
15419         goto defaultlabel;
15420
15421       case DemotePiece:
15422         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15423            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15424             selection = (ChessSquare) (DEMOTED(piece));
15425         } else if(piece == EmptySquare) selection = BlackSilver;
15426         else selection = (ChessSquare)((int)piece + 1);
15427         goto defaultlabel;
15428
15429       case WhiteQueen:
15430       case BlackQueen:
15431         if(gameInfo.variant == VariantShatranj ||
15432            gameInfo.variant == VariantXiangqi  ||
15433            gameInfo.variant == VariantCourier  ||
15434            gameInfo.variant == VariantASEAN    ||
15435            gameInfo.variant == VariantMakruk     )
15436             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15437         goto defaultlabel;
15438
15439       case WhiteKing:
15440       case BlackKing:
15441         if(gameInfo.variant == VariantXiangqi)
15442             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15443         if(gameInfo.variant == VariantKnightmate)
15444             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15445       default:
15446         defaultlabel:
15447         if (gameMode == IcsExamining) {
15448             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15449             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15450                      PieceToChar(selection), AAA + x, ONE + y);
15451             SendToICS(buf);
15452         } else {
15453             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15454                 int n;
15455                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15456                     n = PieceToNumber(selection - BlackPawn);
15457                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15458                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15459                     boards[0][BOARD_HEIGHT-1-n][1]++;
15460                 } else
15461                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15462                     n = PieceToNumber(selection);
15463                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15464                     boards[0][n][BOARD_WIDTH-1] = selection;
15465                     boards[0][n][BOARD_WIDTH-2]++;
15466                 }
15467             } else
15468             boards[0][y][x] = selection;
15469             DrawPosition(TRUE, boards[0]);
15470             ClearHighlights();
15471             fromX = fromY = -1;
15472         }
15473         break;
15474     }
15475 }
15476
15477
15478 void
15479 DropMenuEvent (ChessSquare selection, int x, int y)
15480 {
15481     ChessMove moveType;
15482
15483     switch (gameMode) {
15484       case IcsPlayingWhite:
15485       case MachinePlaysBlack:
15486         if (!WhiteOnMove(currentMove)) {
15487             DisplayMoveError(_("It is Black's turn"));
15488             return;
15489         }
15490         moveType = WhiteDrop;
15491         break;
15492       case IcsPlayingBlack:
15493       case MachinePlaysWhite:
15494         if (WhiteOnMove(currentMove)) {
15495             DisplayMoveError(_("It is White's turn"));
15496             return;
15497         }
15498         moveType = BlackDrop;
15499         break;
15500       case EditGame:
15501         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15502         break;
15503       default:
15504         return;
15505     }
15506
15507     if (moveType == BlackDrop && selection < BlackPawn) {
15508       selection = (ChessSquare) ((int) selection
15509                                  + (int) BlackPawn - (int) WhitePawn);
15510     }
15511     if (boards[currentMove][y][x] != EmptySquare) {
15512         DisplayMoveError(_("That square is occupied"));
15513         return;
15514     }
15515
15516     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15517 }
15518
15519 void
15520 AcceptEvent ()
15521 {
15522     /* Accept a pending offer of any kind from opponent */
15523
15524     if (appData.icsActive) {
15525         SendToICS(ics_prefix);
15526         SendToICS("accept\n");
15527     } else if (cmailMsgLoaded) {
15528         if (currentMove == cmailOldMove &&
15529             commentList[cmailOldMove] != NULL &&
15530             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15531                    "Black offers a draw" : "White offers a draw")) {
15532             TruncateGame();
15533             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15534             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15535         } else {
15536             DisplayError(_("There is no pending offer on this move"), 0);
15537             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15538         }
15539     } else {
15540         /* Not used for offers from chess program */
15541     }
15542 }
15543
15544 void
15545 DeclineEvent ()
15546 {
15547     /* Decline a pending offer of any kind from opponent */
15548
15549     if (appData.icsActive) {
15550         SendToICS(ics_prefix);
15551         SendToICS("decline\n");
15552     } else if (cmailMsgLoaded) {
15553         if (currentMove == cmailOldMove &&
15554             commentList[cmailOldMove] != NULL &&
15555             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15556                    "Black offers a draw" : "White offers a draw")) {
15557 #ifdef NOTDEF
15558             AppendComment(cmailOldMove, "Draw declined", TRUE);
15559             DisplayComment(cmailOldMove - 1, "Draw declined");
15560 #endif /*NOTDEF*/
15561         } else {
15562             DisplayError(_("There is no pending offer on this move"), 0);
15563         }
15564     } else {
15565         /* Not used for offers from chess program */
15566     }
15567 }
15568
15569 void
15570 RematchEvent ()
15571 {
15572     /* Issue ICS rematch command */
15573     if (appData.icsActive) {
15574         SendToICS(ics_prefix);
15575         SendToICS("rematch\n");
15576     }
15577 }
15578
15579 void
15580 CallFlagEvent ()
15581 {
15582     /* Call your opponent's flag (claim a win on time) */
15583     if (appData.icsActive) {
15584         SendToICS(ics_prefix);
15585         SendToICS("flag\n");
15586     } else {
15587         switch (gameMode) {
15588           default:
15589             return;
15590           case MachinePlaysWhite:
15591             if (whiteFlag) {
15592                 if (blackFlag)
15593                   GameEnds(GameIsDrawn, "Both players ran out of time",
15594                            GE_PLAYER);
15595                 else
15596                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15597             } else {
15598                 DisplayError(_("Your opponent is not out of time"), 0);
15599             }
15600             break;
15601           case MachinePlaysBlack:
15602             if (blackFlag) {
15603                 if (whiteFlag)
15604                   GameEnds(GameIsDrawn, "Both players ran out of time",
15605                            GE_PLAYER);
15606                 else
15607                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15608             } else {
15609                 DisplayError(_("Your opponent is not out of time"), 0);
15610             }
15611             break;
15612         }
15613     }
15614 }
15615
15616 void
15617 ClockClick (int which)
15618 {       // [HGM] code moved to back-end from winboard.c
15619         if(which) { // black clock
15620           if (gameMode == EditPosition || gameMode == IcsExamining) {
15621             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15622             SetBlackToPlayEvent();
15623           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15624                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15625           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15626           } else if (shiftKey) {
15627             AdjustClock(which, -1);
15628           } else if (gameMode == IcsPlayingWhite ||
15629                      gameMode == MachinePlaysBlack) {
15630             CallFlagEvent();
15631           }
15632         } else { // white clock
15633           if (gameMode == EditPosition || gameMode == IcsExamining) {
15634             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15635             SetWhiteToPlayEvent();
15636           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15637                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15638           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15639           } else if (shiftKey) {
15640             AdjustClock(which, -1);
15641           } else if (gameMode == IcsPlayingBlack ||
15642                    gameMode == MachinePlaysWhite) {
15643             CallFlagEvent();
15644           }
15645         }
15646 }
15647
15648 void
15649 DrawEvent ()
15650 {
15651     /* Offer draw or accept pending draw offer from opponent */
15652
15653     if (appData.icsActive) {
15654         /* Note: tournament rules require draw offers to be
15655            made after you make your move but before you punch
15656            your clock.  Currently ICS doesn't let you do that;
15657            instead, you immediately punch your clock after making
15658            a move, but you can offer a draw at any time. */
15659
15660         SendToICS(ics_prefix);
15661         SendToICS("draw\n");
15662         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15663     } else if (cmailMsgLoaded) {
15664         if (currentMove == cmailOldMove &&
15665             commentList[cmailOldMove] != NULL &&
15666             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15667                    "Black offers a draw" : "White offers a draw")) {
15668             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15669             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15670         } else if (currentMove == cmailOldMove + 1) {
15671             char *offer = WhiteOnMove(cmailOldMove) ?
15672               "White offers a draw" : "Black offers a draw";
15673             AppendComment(currentMove, offer, TRUE);
15674             DisplayComment(currentMove - 1, offer);
15675             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15676         } else {
15677             DisplayError(_("You must make your move before offering a draw"), 0);
15678             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15679         }
15680     } else if (first.offeredDraw) {
15681         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15682     } else {
15683         if (first.sendDrawOffers) {
15684             SendToProgram("draw\n", &first);
15685             userOfferedDraw = TRUE;
15686         }
15687     }
15688 }
15689
15690 void
15691 AdjournEvent ()
15692 {
15693     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15694
15695     if (appData.icsActive) {
15696         SendToICS(ics_prefix);
15697         SendToICS("adjourn\n");
15698     } else {
15699         /* Currently GNU Chess doesn't offer or accept Adjourns */
15700     }
15701 }
15702
15703
15704 void
15705 AbortEvent ()
15706 {
15707     /* Offer Abort or accept pending Abort offer from opponent */
15708
15709     if (appData.icsActive) {
15710         SendToICS(ics_prefix);
15711         SendToICS("abort\n");
15712     } else {
15713         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15714     }
15715 }
15716
15717 void
15718 ResignEvent ()
15719 {
15720     /* Resign.  You can do this even if it's not your turn. */
15721
15722     if (appData.icsActive) {
15723         SendToICS(ics_prefix);
15724         SendToICS("resign\n");
15725     } else {
15726         switch (gameMode) {
15727           case MachinePlaysWhite:
15728             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15729             break;
15730           case MachinePlaysBlack:
15731             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15732             break;
15733           case EditGame:
15734             if (cmailMsgLoaded) {
15735                 TruncateGame();
15736                 if (WhiteOnMove(cmailOldMove)) {
15737                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15738                 } else {
15739                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15740                 }
15741                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15742             }
15743             break;
15744           default:
15745             break;
15746         }
15747     }
15748 }
15749
15750
15751 void
15752 StopObservingEvent ()
15753 {
15754     /* Stop observing current games */
15755     SendToICS(ics_prefix);
15756     SendToICS("unobserve\n");
15757 }
15758
15759 void
15760 StopExaminingEvent ()
15761 {
15762     /* Stop observing current game */
15763     SendToICS(ics_prefix);
15764     SendToICS("unexamine\n");
15765 }
15766
15767 void
15768 ForwardInner (int target)
15769 {
15770     int limit; int oldSeekGraphUp = seekGraphUp;
15771
15772     if (appData.debugMode)
15773         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15774                 target, currentMove, forwardMostMove);
15775
15776     if (gameMode == EditPosition)
15777       return;
15778
15779     seekGraphUp = FALSE;
15780     MarkTargetSquares(1);
15781     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15782
15783     if (gameMode == PlayFromGameFile && !pausing)
15784       PauseEvent();
15785
15786     if (gameMode == IcsExamining && pausing)
15787       limit = pauseExamForwardMostMove;
15788     else
15789       limit = forwardMostMove;
15790
15791     if (target > limit) target = limit;
15792
15793     if (target > 0 && moveList[target - 1][0]) {
15794         int fromX, fromY, toX, toY;
15795         toX = moveList[target - 1][2] - AAA;
15796         toY = moveList[target - 1][3] - ONE;
15797         if (moveList[target - 1][1] == '@') {
15798             if (appData.highlightLastMove) {
15799                 SetHighlights(-1, -1, toX, toY);
15800             }
15801         } else {
15802             int viaX = moveList[target - 1][5] - AAA;
15803             int viaY = moveList[target - 1][6] - ONE;
15804             fromX = moveList[target - 1][0] - AAA;
15805             fromY = moveList[target - 1][1] - ONE;
15806             if (target == currentMove + 1) {
15807                 if(moveList[target - 1][4] == ';') { // multi-leg
15808                     ChessSquare piece = boards[currentMove][viaY][viaX];
15809                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15810                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15811                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15812                     boards[currentMove][viaY][viaX] = piece;
15813                 } else
15814                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15815             }
15816             if (appData.highlightLastMove) {
15817                 SetHighlights(fromX, fromY, toX, toY);
15818             }
15819         }
15820     }
15821     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15822         gameMode == Training || gameMode == PlayFromGameFile ||
15823         gameMode == AnalyzeFile) {
15824         while (currentMove < target) {
15825             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15826             SendMoveToProgram(currentMove++, &first);
15827         }
15828     } else {
15829         currentMove = target;
15830     }
15831
15832     if (gameMode == EditGame || gameMode == EndOfGame) {
15833         whiteTimeRemaining = timeRemaining[0][currentMove];
15834         blackTimeRemaining = timeRemaining[1][currentMove];
15835     }
15836     DisplayBothClocks();
15837     DisplayMove(currentMove - 1);
15838     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15839     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15840     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15841         DisplayComment(currentMove - 1, commentList[currentMove]);
15842     }
15843     ClearMap(); // [HGM] exclude: invalidate map
15844 }
15845
15846
15847 void
15848 ForwardEvent ()
15849 {
15850     if (gameMode == IcsExamining && !pausing) {
15851         SendToICS(ics_prefix);
15852         SendToICS("forward\n");
15853     } else {
15854         ForwardInner(currentMove + 1);
15855     }
15856 }
15857
15858 void
15859 ToEndEvent ()
15860 {
15861     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15862         /* to optimze, we temporarily turn off analysis mode while we feed
15863          * the remaining moves to the engine. Otherwise we get analysis output
15864          * after each move.
15865          */
15866         if (first.analysisSupport) {
15867           SendToProgram("exit\nforce\n", &first);
15868           first.analyzing = FALSE;
15869         }
15870     }
15871
15872     if (gameMode == IcsExamining && !pausing) {
15873         SendToICS(ics_prefix);
15874         SendToICS("forward 999999\n");
15875     } else {
15876         ForwardInner(forwardMostMove);
15877     }
15878
15879     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15880         /* we have fed all the moves, so reactivate analysis mode */
15881         SendToProgram("analyze\n", &first);
15882         first.analyzing = TRUE;
15883         /*first.maybeThinking = TRUE;*/
15884         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15885     }
15886 }
15887
15888 void
15889 BackwardInner (int target)
15890 {
15891     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15892
15893     if (appData.debugMode)
15894         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15895                 target, currentMove, forwardMostMove);
15896
15897     if (gameMode == EditPosition) return;
15898     seekGraphUp = FALSE;
15899     MarkTargetSquares(1);
15900     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15901     if (currentMove <= backwardMostMove) {
15902         ClearHighlights();
15903         DrawPosition(full_redraw, boards[currentMove]);
15904         return;
15905     }
15906     if (gameMode == PlayFromGameFile && !pausing)
15907       PauseEvent();
15908
15909     if (moveList[target][0]) {
15910         int fromX, fromY, toX, toY;
15911         toX = moveList[target][2] - AAA;
15912         toY = moveList[target][3] - ONE;
15913         if (moveList[target][1] == '@') {
15914             if (appData.highlightLastMove) {
15915                 SetHighlights(-1, -1, toX, toY);
15916             }
15917         } else {
15918             fromX = moveList[target][0] - AAA;
15919             fromY = moveList[target][1] - ONE;
15920             if (target == currentMove - 1) {
15921                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15922             }
15923             if (appData.highlightLastMove) {
15924                 SetHighlights(fromX, fromY, toX, toY);
15925             }
15926         }
15927     }
15928     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15929         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15930         while (currentMove > target) {
15931             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15932                 // null move cannot be undone. Reload program with move history before it.
15933                 int i;
15934                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15935                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15936                 }
15937                 SendBoard(&first, i);
15938               if(second.analyzing) SendBoard(&second, i);
15939                 for(currentMove=i; currentMove<target; currentMove++) {
15940                     SendMoveToProgram(currentMove, &first);
15941                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15942                 }
15943                 break;
15944             }
15945             SendToBoth("undo\n");
15946             currentMove--;
15947         }
15948     } else {
15949         currentMove = target;
15950     }
15951
15952     if (gameMode == EditGame || gameMode == EndOfGame) {
15953         whiteTimeRemaining = timeRemaining[0][currentMove];
15954         blackTimeRemaining = timeRemaining[1][currentMove];
15955     }
15956     DisplayBothClocks();
15957     DisplayMove(currentMove - 1);
15958     DrawPosition(full_redraw, boards[currentMove]);
15959     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15960     // [HGM] PV info: routine tests if comment empty
15961     DisplayComment(currentMove - 1, commentList[currentMove]);
15962     ClearMap(); // [HGM] exclude: invalidate map
15963 }
15964
15965 void
15966 BackwardEvent ()
15967 {
15968     if (gameMode == IcsExamining && !pausing) {
15969         SendToICS(ics_prefix);
15970         SendToICS("backward\n");
15971     } else {
15972         BackwardInner(currentMove - 1);
15973     }
15974 }
15975
15976 void
15977 ToStartEvent ()
15978 {
15979     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15980         /* to optimize, we temporarily turn off analysis mode while we undo
15981          * all the moves. Otherwise we get analysis output after each undo.
15982          */
15983         if (first.analysisSupport) {
15984           SendToProgram("exit\nforce\n", &first);
15985           first.analyzing = FALSE;
15986         }
15987     }
15988
15989     if (gameMode == IcsExamining && !pausing) {
15990         SendToICS(ics_prefix);
15991         SendToICS("backward 999999\n");
15992     } else {
15993         BackwardInner(backwardMostMove);
15994     }
15995
15996     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15997         /* we have fed all the moves, so reactivate analysis mode */
15998         SendToProgram("analyze\n", &first);
15999         first.analyzing = TRUE;
16000         /*first.maybeThinking = TRUE;*/
16001         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16002     }
16003 }
16004
16005 void
16006 ToNrEvent (int to)
16007 {
16008   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16009   if (to >= forwardMostMove) to = forwardMostMove;
16010   if (to <= backwardMostMove) to = backwardMostMove;
16011   if (to < currentMove) {
16012     BackwardInner(to);
16013   } else {
16014     ForwardInner(to);
16015   }
16016 }
16017
16018 void
16019 RevertEvent (Boolean annotate)
16020 {
16021     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16022         return;
16023     }
16024     if (gameMode != IcsExamining) {
16025         DisplayError(_("You are not examining a game"), 0);
16026         return;
16027     }
16028     if (pausing) {
16029         DisplayError(_("You can't revert while pausing"), 0);
16030         return;
16031     }
16032     SendToICS(ics_prefix);
16033     SendToICS("revert\n");
16034 }
16035
16036 void
16037 RetractMoveEvent ()
16038 {
16039     switch (gameMode) {
16040       case MachinePlaysWhite:
16041       case MachinePlaysBlack:
16042         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16043             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16044             return;
16045         }
16046         if (forwardMostMove < 2) return;
16047         currentMove = forwardMostMove = forwardMostMove - 2;
16048         whiteTimeRemaining = timeRemaining[0][currentMove];
16049         blackTimeRemaining = timeRemaining[1][currentMove];
16050         DisplayBothClocks();
16051         DisplayMove(currentMove - 1);
16052         ClearHighlights();/*!! could figure this out*/
16053         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16054         SendToProgram("remove\n", &first);
16055         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16056         break;
16057
16058       case BeginningOfGame:
16059       default:
16060         break;
16061
16062       case IcsPlayingWhite:
16063       case IcsPlayingBlack:
16064         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16065             SendToICS(ics_prefix);
16066             SendToICS("takeback 2\n");
16067         } else {
16068             SendToICS(ics_prefix);
16069             SendToICS("takeback 1\n");
16070         }
16071         break;
16072     }
16073 }
16074
16075 void
16076 MoveNowEvent ()
16077 {
16078     ChessProgramState *cps;
16079
16080     switch (gameMode) {
16081       case MachinePlaysWhite:
16082         if (!WhiteOnMove(forwardMostMove)) {
16083             DisplayError(_("It is your turn"), 0);
16084             return;
16085         }
16086         cps = &first;
16087         break;
16088       case MachinePlaysBlack:
16089         if (WhiteOnMove(forwardMostMove)) {
16090             DisplayError(_("It is your turn"), 0);
16091             return;
16092         }
16093         cps = &first;
16094         break;
16095       case TwoMachinesPlay:
16096         if (WhiteOnMove(forwardMostMove) ==
16097             (first.twoMachinesColor[0] == 'w')) {
16098             cps = &first;
16099         } else {
16100             cps = &second;
16101         }
16102         break;
16103       case BeginningOfGame:
16104       default:
16105         return;
16106     }
16107     SendToProgram("?\n", cps);
16108 }
16109
16110 void
16111 TruncateGameEvent ()
16112 {
16113     EditGameEvent();
16114     if (gameMode != EditGame) return;
16115     TruncateGame();
16116 }
16117
16118 void
16119 TruncateGame ()
16120 {
16121     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16122     if (forwardMostMove > currentMove) {
16123         if (gameInfo.resultDetails != NULL) {
16124             free(gameInfo.resultDetails);
16125             gameInfo.resultDetails = NULL;
16126             gameInfo.result = GameUnfinished;
16127         }
16128         forwardMostMove = currentMove;
16129         HistorySet(parseList, backwardMostMove, forwardMostMove,
16130                    currentMove-1);
16131     }
16132 }
16133
16134 void
16135 HintEvent ()
16136 {
16137     if (appData.noChessProgram) return;
16138     switch (gameMode) {
16139       case MachinePlaysWhite:
16140         if (WhiteOnMove(forwardMostMove)) {
16141             DisplayError(_("Wait until your turn."), 0);
16142             return;
16143         }
16144         break;
16145       case BeginningOfGame:
16146       case MachinePlaysBlack:
16147         if (!WhiteOnMove(forwardMostMove)) {
16148             DisplayError(_("Wait until your turn."), 0);
16149             return;
16150         }
16151         break;
16152       default:
16153         DisplayError(_("No hint available"), 0);
16154         return;
16155     }
16156     SendToProgram("hint\n", &first);
16157     hintRequested = TRUE;
16158 }
16159
16160 int
16161 SaveSelected (FILE *g, int dummy, char *dummy2)
16162 {
16163     ListGame * lg = (ListGame *) gameList.head;
16164     int nItem, cnt=0;
16165     FILE *f;
16166
16167     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16168         DisplayError(_("Game list not loaded or empty"), 0);
16169         return 0;
16170     }
16171
16172     creatingBook = TRUE; // suppresses stuff during load game
16173
16174     /* Get list size */
16175     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16176         if(lg->position >= 0) { // selected?
16177             LoadGame(f, nItem, "", TRUE);
16178             SaveGamePGN2(g); // leaves g open
16179             cnt++; DoEvents();
16180         }
16181         lg = (ListGame *) lg->node.succ;
16182     }
16183
16184     fclose(g);
16185     creatingBook = FALSE;
16186
16187     return cnt;
16188 }
16189
16190 void
16191 CreateBookEvent ()
16192 {
16193     ListGame * lg = (ListGame *) gameList.head;
16194     FILE *f, *g;
16195     int nItem;
16196     static int secondTime = FALSE;
16197
16198     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16199         DisplayError(_("Game list not loaded or empty"), 0);
16200         return;
16201     }
16202
16203     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16204         fclose(g);
16205         secondTime++;
16206         DisplayNote(_("Book file exists! Try again for overwrite."));
16207         return;
16208     }
16209
16210     creatingBook = TRUE;
16211     secondTime = FALSE;
16212
16213     /* Get list size */
16214     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16215         if(lg->position >= 0) {
16216             LoadGame(f, nItem, "", TRUE);
16217             AddGameToBook(TRUE);
16218             DoEvents();
16219         }
16220         lg = (ListGame *) lg->node.succ;
16221     }
16222
16223     creatingBook = FALSE;
16224     FlushBook();
16225 }
16226
16227 void
16228 BookEvent ()
16229 {
16230     if (appData.noChessProgram) return;
16231     switch (gameMode) {
16232       case MachinePlaysWhite:
16233         if (WhiteOnMove(forwardMostMove)) {
16234             DisplayError(_("Wait until your turn."), 0);
16235             return;
16236         }
16237         break;
16238       case BeginningOfGame:
16239       case MachinePlaysBlack:
16240         if (!WhiteOnMove(forwardMostMove)) {
16241             DisplayError(_("Wait until your turn."), 0);
16242             return;
16243         }
16244         break;
16245       case EditPosition:
16246         EditPositionDone(TRUE);
16247         break;
16248       case TwoMachinesPlay:
16249         return;
16250       default:
16251         break;
16252     }
16253     SendToProgram("bk\n", &first);
16254     bookOutput[0] = NULLCHAR;
16255     bookRequested = TRUE;
16256 }
16257
16258 void
16259 AboutGameEvent ()
16260 {
16261     char *tags = PGNTags(&gameInfo);
16262     TagsPopUp(tags, CmailMsg());
16263     free(tags);
16264 }
16265
16266 /* end button procedures */
16267
16268 void
16269 PrintPosition (FILE *fp, int move)
16270 {
16271     int i, j;
16272
16273     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16274         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16275             char c = PieceToChar(boards[move][i][j]);
16276             fputc(c == 'x' ? '.' : c, fp);
16277             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16278         }
16279     }
16280     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16281       fprintf(fp, "white to play\n");
16282     else
16283       fprintf(fp, "black to play\n");
16284 }
16285
16286 void
16287 PrintOpponents (FILE *fp)
16288 {
16289     if (gameInfo.white != NULL) {
16290         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16291     } else {
16292         fprintf(fp, "\n");
16293     }
16294 }
16295
16296 /* Find last component of program's own name, using some heuristics */
16297 void
16298 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16299 {
16300     char *p, *q, c;
16301     int local = (strcmp(host, "localhost") == 0);
16302     while (!local && (p = strchr(prog, ';')) != NULL) {
16303         p++;
16304         while (*p == ' ') p++;
16305         prog = p;
16306     }
16307     if (*prog == '"' || *prog == '\'') {
16308         q = strchr(prog + 1, *prog);
16309     } else {
16310         q = strchr(prog, ' ');
16311     }
16312     if (q == NULL) q = prog + strlen(prog);
16313     p = q;
16314     while (p >= prog && *p != '/' && *p != '\\') p--;
16315     p++;
16316     if(p == prog && *p == '"') p++;
16317     c = *q; *q = 0;
16318     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16319     memcpy(buf, p, q - p);
16320     buf[q - p] = NULLCHAR;
16321     if (!local) {
16322         strcat(buf, "@");
16323         strcat(buf, host);
16324     }
16325 }
16326
16327 char *
16328 TimeControlTagValue ()
16329 {
16330     char buf[MSG_SIZ];
16331     if (!appData.clockMode) {
16332       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16333     } else if (movesPerSession > 0) {
16334       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16335     } else if (timeIncrement == 0) {
16336       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16337     } else {
16338       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16339     }
16340     return StrSave(buf);
16341 }
16342
16343 void
16344 SetGameInfo ()
16345 {
16346     /* This routine is used only for certain modes */
16347     VariantClass v = gameInfo.variant;
16348     ChessMove r = GameUnfinished;
16349     char *p = NULL;
16350
16351     if(keepInfo) return;
16352
16353     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16354         r = gameInfo.result;
16355         p = gameInfo.resultDetails;
16356         gameInfo.resultDetails = NULL;
16357     }
16358     ClearGameInfo(&gameInfo);
16359     gameInfo.variant = v;
16360
16361     switch (gameMode) {
16362       case MachinePlaysWhite:
16363         gameInfo.event = StrSave( appData.pgnEventHeader );
16364         gameInfo.site = StrSave(HostName());
16365         gameInfo.date = PGNDate();
16366         gameInfo.round = StrSave("-");
16367         gameInfo.white = StrSave(first.tidy);
16368         gameInfo.black = StrSave(UserName());
16369         gameInfo.timeControl = TimeControlTagValue();
16370         break;
16371
16372       case MachinePlaysBlack:
16373         gameInfo.event = StrSave( appData.pgnEventHeader );
16374         gameInfo.site = StrSave(HostName());
16375         gameInfo.date = PGNDate();
16376         gameInfo.round = StrSave("-");
16377         gameInfo.white = StrSave(UserName());
16378         gameInfo.black = StrSave(first.tidy);
16379         gameInfo.timeControl = TimeControlTagValue();
16380         break;
16381
16382       case TwoMachinesPlay:
16383         gameInfo.event = StrSave( appData.pgnEventHeader );
16384         gameInfo.site = StrSave(HostName());
16385         gameInfo.date = PGNDate();
16386         if (roundNr > 0) {
16387             char buf[MSG_SIZ];
16388             snprintf(buf, MSG_SIZ, "%d", roundNr);
16389             gameInfo.round = StrSave(buf);
16390         } else {
16391             gameInfo.round = StrSave("-");
16392         }
16393         if (first.twoMachinesColor[0] == 'w') {
16394             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16395             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16396         } else {
16397             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16398             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16399         }
16400         gameInfo.timeControl = TimeControlTagValue();
16401         break;
16402
16403       case EditGame:
16404         gameInfo.event = StrSave("Edited game");
16405         gameInfo.site = StrSave(HostName());
16406         gameInfo.date = PGNDate();
16407         gameInfo.round = StrSave("-");
16408         gameInfo.white = StrSave("-");
16409         gameInfo.black = StrSave("-");
16410         gameInfo.result = r;
16411         gameInfo.resultDetails = p;
16412         break;
16413
16414       case EditPosition:
16415         gameInfo.event = StrSave("Edited position");
16416         gameInfo.site = StrSave(HostName());
16417         gameInfo.date = PGNDate();
16418         gameInfo.round = StrSave("-");
16419         gameInfo.white = StrSave("-");
16420         gameInfo.black = StrSave("-");
16421         break;
16422
16423       case IcsPlayingWhite:
16424       case IcsPlayingBlack:
16425       case IcsObserving:
16426       case IcsExamining:
16427         break;
16428
16429       case PlayFromGameFile:
16430         gameInfo.event = StrSave("Game from non-PGN file");
16431         gameInfo.site = StrSave(HostName());
16432         gameInfo.date = PGNDate();
16433         gameInfo.round = StrSave("-");
16434         gameInfo.white = StrSave("?");
16435         gameInfo.black = StrSave("?");
16436         break;
16437
16438       default:
16439         break;
16440     }
16441 }
16442
16443 void
16444 ReplaceComment (int index, char *text)
16445 {
16446     int len;
16447     char *p;
16448     float score;
16449
16450     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16451        pvInfoList[index-1].depth == len &&
16452        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16453        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16454     while (*text == '\n') text++;
16455     len = strlen(text);
16456     while (len > 0 && text[len - 1] == '\n') len--;
16457
16458     if (commentList[index] != NULL)
16459       free(commentList[index]);
16460
16461     if (len == 0) {
16462         commentList[index] = NULL;
16463         return;
16464     }
16465   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16466       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16467       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16468     commentList[index] = (char *) malloc(len + 2);
16469     strncpy(commentList[index], text, len);
16470     commentList[index][len] = '\n';
16471     commentList[index][len + 1] = NULLCHAR;
16472   } else {
16473     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16474     char *p;
16475     commentList[index] = (char *) malloc(len + 7);
16476     safeStrCpy(commentList[index], "{\n", 3);
16477     safeStrCpy(commentList[index]+2, text, len+1);
16478     commentList[index][len+2] = NULLCHAR;
16479     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16480     strcat(commentList[index], "\n}\n");
16481   }
16482 }
16483
16484 void
16485 CrushCRs (char *text)
16486 {
16487   char *p = text;
16488   char *q = text;
16489   char ch;
16490
16491   do {
16492     ch = *p++;
16493     if (ch == '\r') continue;
16494     *q++ = ch;
16495   } while (ch != '\0');
16496 }
16497
16498 void
16499 AppendComment (int index, char *text, Boolean addBraces)
16500 /* addBraces  tells if we should add {} */
16501 {
16502     int oldlen, len;
16503     char *old;
16504
16505 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16506     if(addBraces == 3) addBraces = 0; else // force appending literally
16507     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16508
16509     CrushCRs(text);
16510     while (*text == '\n') text++;
16511     len = strlen(text);
16512     while (len > 0 && text[len - 1] == '\n') len--;
16513     text[len] = NULLCHAR;
16514
16515     if (len == 0) return;
16516
16517     if (commentList[index] != NULL) {
16518       Boolean addClosingBrace = addBraces;
16519         old = commentList[index];
16520         oldlen = strlen(old);
16521         while(commentList[index][oldlen-1] ==  '\n')
16522           commentList[index][--oldlen] = NULLCHAR;
16523         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16524         safeStrCpy(commentList[index], old, oldlen + len + 6);
16525         free(old);
16526         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16527         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16528           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16529           while (*text == '\n') { text++; len--; }
16530           commentList[index][--oldlen] = NULLCHAR;
16531       }
16532         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16533         else          strcat(commentList[index], "\n");
16534         strcat(commentList[index], text);
16535         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16536         else          strcat(commentList[index], "\n");
16537     } else {
16538         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16539         if(addBraces)
16540           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16541         else commentList[index][0] = NULLCHAR;
16542         strcat(commentList[index], text);
16543         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16544         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16545     }
16546 }
16547
16548 static char *
16549 FindStr (char * text, char * sub_text)
16550 {
16551     char * result = strstr( text, sub_text );
16552
16553     if( result != NULL ) {
16554         result += strlen( sub_text );
16555     }
16556
16557     return result;
16558 }
16559
16560 /* [AS] Try to extract PV info from PGN comment */
16561 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16562 char *
16563 GetInfoFromComment (int index, char * text)
16564 {
16565     char * sep = text, *p;
16566
16567     if( text != NULL && index > 0 ) {
16568         int score = 0;
16569         int depth = 0;
16570         int time = -1, sec = 0, deci;
16571         char * s_eval = FindStr( text, "[%eval " );
16572         char * s_emt = FindStr( text, "[%emt " );
16573 #if 0
16574         if( s_eval != NULL || s_emt != NULL ) {
16575 #else
16576         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16577 #endif
16578             /* New style */
16579             char delim;
16580
16581             if( s_eval != NULL ) {
16582                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16583                     return text;
16584                 }
16585
16586                 if( delim != ']' ) {
16587                     return text;
16588                 }
16589             }
16590
16591             if( s_emt != NULL ) {
16592             }
16593                 return text;
16594         }
16595         else {
16596             /* We expect something like: [+|-]nnn.nn/dd */
16597             int score_lo = 0;
16598
16599             if(*text != '{') return text; // [HGM] braces: must be normal comment
16600
16601             sep = strchr( text, '/' );
16602             if( sep == NULL || sep < (text+4) ) {
16603                 return text;
16604             }
16605
16606             p = text;
16607             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16608             if(p[1] == '(') { // comment starts with PV
16609                p = strchr(p, ')'); // locate end of PV
16610                if(p == NULL || sep < p+5) return text;
16611                // at this point we have something like "{(.*) +0.23/6 ..."
16612                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16613                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16614                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16615             }
16616             time = -1; sec = -1; deci = -1;
16617             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16618                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16619                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16620                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16621                 return text;
16622             }
16623
16624             if( score_lo < 0 || score_lo >= 100 ) {
16625                 return text;
16626             }
16627
16628             if(sec >= 0) time = 600*time + 10*sec; else
16629             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16630
16631             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16632
16633             /* [HGM] PV time: now locate end of PV info */
16634             while( *++sep >= '0' && *sep <= '9'); // strip depth
16635             if(time >= 0)
16636             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16637             if(sec >= 0)
16638             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16639             if(deci >= 0)
16640             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16641             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16642         }
16643
16644         if( depth <= 0 ) {
16645             return text;
16646         }
16647
16648         if( time < 0 ) {
16649             time = -1;
16650         }
16651
16652         pvInfoList[index-1].depth = depth;
16653         pvInfoList[index-1].score = score;
16654         pvInfoList[index-1].time  = 10*time; // centi-sec
16655         if(*sep == '}') *sep = 0; else *--sep = '{';
16656         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16657     }
16658     return sep;
16659 }
16660
16661 void
16662 SendToProgram (char *message, ChessProgramState *cps)
16663 {
16664     int count, outCount, error;
16665     char buf[MSG_SIZ];
16666
16667     if (cps->pr == NoProc) return;
16668     Attention(cps);
16669
16670     if (appData.debugMode) {
16671         TimeMark now;
16672         GetTimeMark(&now);
16673         fprintf(debugFP, "%ld >%-6s: %s",
16674                 SubtractTimeMarks(&now, &programStartTime),
16675                 cps->which, message);
16676         if(serverFP)
16677             fprintf(serverFP, "%ld >%-6s: %s",
16678                 SubtractTimeMarks(&now, &programStartTime),
16679                 cps->which, message), fflush(serverFP);
16680     }
16681
16682     count = strlen(message);
16683     outCount = OutputToProcess(cps->pr, message, count, &error);
16684     if (outCount < count && !exiting
16685                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16686       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16687       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16688         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16689             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16690                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16691                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16692                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16693             } else {
16694                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16695                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16696                 gameInfo.result = res;
16697             }
16698             gameInfo.resultDetails = StrSave(buf);
16699         }
16700         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16701         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16702     }
16703 }
16704
16705 void
16706 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16707 {
16708     char *end_str;
16709     char buf[MSG_SIZ];
16710     ChessProgramState *cps = (ChessProgramState *)closure;
16711
16712     if (isr != cps->isr) return; /* Killed intentionally */
16713     if (count <= 0) {
16714         if (count == 0) {
16715             RemoveInputSource(cps->isr);
16716             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16717                     _(cps->which), cps->program);
16718             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16719             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16720                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16721                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16722                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16723                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16724                 } else {
16725                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16726                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16727                     gameInfo.result = res;
16728                 }
16729                 gameInfo.resultDetails = StrSave(buf);
16730             }
16731             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16732             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16733         } else {
16734             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16735                     _(cps->which), cps->program);
16736             RemoveInputSource(cps->isr);
16737
16738             /* [AS] Program is misbehaving badly... kill it */
16739             if( count == -2 ) {
16740                 DestroyChildProcess( cps->pr, 9 );
16741                 cps->pr = NoProc;
16742             }
16743
16744             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16745         }
16746         return;
16747     }
16748
16749     if ((end_str = strchr(message, '\r')) != NULL)
16750       *end_str = NULLCHAR;
16751     if ((end_str = strchr(message, '\n')) != NULL)
16752       *end_str = NULLCHAR;
16753
16754     if (appData.debugMode) {
16755         TimeMark now; int print = 1;
16756         char *quote = ""; char c; int i;
16757
16758         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16759                 char start = message[0];
16760                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16761                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16762                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16763                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16764                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16765                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16766                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16767                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16768                    sscanf(message, "hint: %c", &c)!=1 &&
16769                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16770                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16771                     print = (appData.engineComments >= 2);
16772                 }
16773                 message[0] = start; // restore original message
16774         }
16775         if(print) {
16776                 GetTimeMark(&now);
16777                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16778                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16779                         quote,
16780                         message);
16781                 if(serverFP)
16782                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16783                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16784                         quote,
16785                         message), fflush(serverFP);
16786         }
16787     }
16788
16789     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16790     if (appData.icsEngineAnalyze) {
16791         if (strstr(message, "whisper") != NULL ||
16792              strstr(message, "kibitz") != NULL ||
16793             strstr(message, "tellics") != NULL) return;
16794     }
16795
16796     HandleMachineMove(message, cps);
16797 }
16798
16799
16800 void
16801 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16802 {
16803     char buf[MSG_SIZ];
16804     int seconds;
16805
16806     if( timeControl_2 > 0 ) {
16807         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16808             tc = timeControl_2;
16809         }
16810     }
16811     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16812     inc /= cps->timeOdds;
16813     st  /= cps->timeOdds;
16814
16815     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16816
16817     if (st > 0) {
16818       /* Set exact time per move, normally using st command */
16819       if (cps->stKludge) {
16820         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16821         seconds = st % 60;
16822         if (seconds == 0) {
16823           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16824         } else {
16825           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16826         }
16827       } else {
16828         snprintf(buf, MSG_SIZ, "st %d\n", st);
16829       }
16830     } else {
16831       /* Set conventional or incremental time control, using level command */
16832       if (seconds == 0) {
16833         /* Note old gnuchess bug -- minutes:seconds used to not work.
16834            Fixed in later versions, but still avoid :seconds
16835            when seconds is 0. */
16836         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16837       } else {
16838         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16839                  seconds, inc/1000.);
16840       }
16841     }
16842     SendToProgram(buf, cps);
16843
16844     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16845     /* Orthogonally, limit search to given depth */
16846     if (sd > 0) {
16847       if (cps->sdKludge) {
16848         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16849       } else {
16850         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16851       }
16852       SendToProgram(buf, cps);
16853     }
16854
16855     if(cps->nps >= 0) { /* [HGM] nps */
16856         if(cps->supportsNPS == FALSE)
16857           cps->nps = -1; // don't use if engine explicitly says not supported!
16858         else {
16859           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16860           SendToProgram(buf, cps);
16861         }
16862     }
16863 }
16864
16865 ChessProgramState *
16866 WhitePlayer ()
16867 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16868 {
16869     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16870        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16871         return &second;
16872     return &first;
16873 }
16874
16875 void
16876 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16877 {
16878     char message[MSG_SIZ];
16879     long time, otime;
16880
16881     /* Note: this routine must be called when the clocks are stopped
16882        or when they have *just* been set or switched; otherwise
16883        it will be off by the time since the current tick started.
16884     */
16885     if (machineWhite) {
16886         time = whiteTimeRemaining / 10;
16887         otime = blackTimeRemaining / 10;
16888     } else {
16889         time = blackTimeRemaining / 10;
16890         otime = whiteTimeRemaining / 10;
16891     }
16892     /* [HGM] translate opponent's time by time-odds factor */
16893     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16894
16895     if (time <= 0) time = 1;
16896     if (otime <= 0) otime = 1;
16897
16898     snprintf(message, MSG_SIZ, "time %ld\n", time);
16899     SendToProgram(message, cps);
16900
16901     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16902     SendToProgram(message, cps);
16903 }
16904
16905 char *
16906 EngineDefinedVariant (ChessProgramState *cps, int n)
16907 {   // return name of n-th unknown variant that engine supports
16908     static char buf[MSG_SIZ];
16909     char *p, *s = cps->variants;
16910     if(!s) return NULL;
16911     do { // parse string from variants feature
16912       VariantClass v;
16913         p = strchr(s, ',');
16914         if(p) *p = NULLCHAR;
16915       v = StringToVariant(s);
16916       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16917         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16918             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16919                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16920                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16921                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16922             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16923         }
16924         if(p) *p++ = ',';
16925         if(n < 0) return buf;
16926     } while(s = p);
16927     return NULL;
16928 }
16929
16930 int
16931 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16932 {
16933   char buf[MSG_SIZ];
16934   int len = strlen(name);
16935   int val;
16936
16937   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16938     (*p) += len + 1;
16939     sscanf(*p, "%d", &val);
16940     *loc = (val != 0);
16941     while (**p && **p != ' ')
16942       (*p)++;
16943     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16944     SendToProgram(buf, cps);
16945     return TRUE;
16946   }
16947   return FALSE;
16948 }
16949
16950 int
16951 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16952 {
16953   char buf[MSG_SIZ];
16954   int len = strlen(name);
16955   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16956     (*p) += len + 1;
16957     sscanf(*p, "%d", loc);
16958     while (**p && **p != ' ') (*p)++;
16959     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16960     SendToProgram(buf, cps);
16961     return TRUE;
16962   }
16963   return FALSE;
16964 }
16965
16966 int
16967 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16968 {
16969   char buf[MSG_SIZ];
16970   int len = strlen(name);
16971   if (strncmp((*p), name, len) == 0
16972       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16973     (*p) += len + 2;
16974     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16975     sscanf(*p, "%[^\"]", *loc);
16976     while (**p && **p != '\"') (*p)++;
16977     if (**p == '\"') (*p)++;
16978     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16979     SendToProgram(buf, cps);
16980     return TRUE;
16981   }
16982   return FALSE;
16983 }
16984
16985 int
16986 ParseOption (Option *opt, ChessProgramState *cps)
16987 // [HGM] options: process the string that defines an engine option, and determine
16988 // name, type, default value, and allowed value range
16989 {
16990         char *p, *q, buf[MSG_SIZ];
16991         int n, min = (-1)<<31, max = 1<<31, def;
16992
16993         opt->target = &opt->value;   // OK for spin/slider and checkbox
16994         if(p = strstr(opt->name, " -spin ")) {
16995             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16996             if(max < min) max = min; // enforce consistency
16997             if(def < min) def = min;
16998             if(def > max) def = max;
16999             opt->value = def;
17000             opt->min = min;
17001             opt->max = max;
17002             opt->type = Spin;
17003         } else if((p = strstr(opt->name, " -slider "))) {
17004             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17005             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17006             if(max < min) max = min; // enforce consistency
17007             if(def < min) def = min;
17008             if(def > max) def = max;
17009             opt->value = def;
17010             opt->min = min;
17011             opt->max = max;
17012             opt->type = Spin; // Slider;
17013         } else if((p = strstr(opt->name, " -string "))) {
17014             opt->textValue = p+9;
17015             opt->type = TextBox;
17016             opt->target = &opt->textValue;
17017         } else if((p = strstr(opt->name, " -file "))) {
17018             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17019             opt->target = opt->textValue = p+7;
17020             opt->type = FileName; // FileName;
17021             opt->target = &opt->textValue;
17022         } else if((p = strstr(opt->name, " -path "))) {
17023             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17024             opt->target = opt->textValue = p+7;
17025             opt->type = PathName; // PathName;
17026             opt->target = &opt->textValue;
17027         } else if(p = strstr(opt->name, " -check ")) {
17028             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17029             opt->value = (def != 0);
17030             opt->type = CheckBox;
17031         } else if(p = strstr(opt->name, " -combo ")) {
17032             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17033             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17034             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17035             opt->value = n = 0;
17036             while(q = StrStr(q, " /// ")) {
17037                 n++; *q = 0;    // count choices, and null-terminate each of them
17038                 q += 5;
17039                 if(*q == '*') { // remember default, which is marked with * prefix
17040                     q++;
17041                     opt->value = n;
17042                 }
17043                 cps->comboList[cps->comboCnt++] = q;
17044             }
17045             cps->comboList[cps->comboCnt++] = NULL;
17046             opt->max = n + 1;
17047             opt->type = ComboBox;
17048         } else if(p = strstr(opt->name, " -button")) {
17049             opt->type = Button;
17050         } else if(p = strstr(opt->name, " -save")) {
17051             opt->type = SaveButton;
17052         } else return FALSE;
17053         *p = 0; // terminate option name
17054         // now look if the command-line options define a setting for this engine option.
17055         if(cps->optionSettings && cps->optionSettings[0])
17056             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17057         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17058           snprintf(buf, MSG_SIZ, "option %s", p);
17059                 if(p = strstr(buf, ",")) *p = 0;
17060                 if(q = strchr(buf, '=')) switch(opt->type) {
17061                     case ComboBox:
17062                         for(n=0; n<opt->max; n++)
17063                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17064                         break;
17065                     case TextBox:
17066                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17067                         break;
17068                     case Spin:
17069                     case CheckBox:
17070                         opt->value = atoi(q+1);
17071                     default:
17072                         break;
17073                 }
17074                 strcat(buf, "\n");
17075                 SendToProgram(buf, cps);
17076         }
17077         return TRUE;
17078 }
17079
17080 void
17081 FeatureDone (ChessProgramState *cps, int val)
17082 {
17083   DelayedEventCallback cb = GetDelayedEvent();
17084   if ((cb == InitBackEnd3 && cps == &first) ||
17085       (cb == SettingsMenuIfReady && cps == &second) ||
17086       (cb == LoadEngine) ||
17087       (cb == TwoMachinesEventIfReady)) {
17088     CancelDelayedEvent();
17089     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17090   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17091   cps->initDone = val;
17092   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17093 }
17094
17095 /* Parse feature command from engine */
17096 void
17097 ParseFeatures (char *args, ChessProgramState *cps)
17098 {
17099   char *p = args;
17100   char *q = NULL;
17101   int val;
17102   char buf[MSG_SIZ];
17103
17104   for (;;) {
17105     while (*p == ' ') p++;
17106     if (*p == NULLCHAR) return;
17107
17108     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17109     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17110     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17111     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17112     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17113     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17114     if (BoolFeature(&p, "reuse", &val, cps)) {
17115       /* Engine can disable reuse, but can't enable it if user said no */
17116       if (!val) cps->reuse = FALSE;
17117       continue;
17118     }
17119     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17120     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17121       if (gameMode == TwoMachinesPlay) {
17122         DisplayTwoMachinesTitle();
17123       } else {
17124         DisplayTitle("");
17125       }
17126       continue;
17127     }
17128     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17129     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17130     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17131     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17132     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17133     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17134     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17135     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17136     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17137     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17138     if (IntFeature(&p, "done", &val, cps)) {
17139       FeatureDone(cps, val);
17140       continue;
17141     }
17142     /* Added by Tord: */
17143     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17144     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17145     /* End of additions by Tord */
17146
17147     /* [HGM] added features: */
17148     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17149     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17150     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17151     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17152     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17153     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17154     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17155     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17156         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17157         FREE(cps->option[cps->nrOptions].name);
17158         cps->option[cps->nrOptions].name = q; q = NULL;
17159         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17160           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17161             SendToProgram(buf, cps);
17162             continue;
17163         }
17164         if(cps->nrOptions >= MAX_OPTIONS) {
17165             cps->nrOptions--;
17166             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17167             DisplayError(buf, 0);
17168         }
17169         continue;
17170     }
17171     /* End of additions by HGM */
17172
17173     /* unknown feature: complain and skip */
17174     q = p;
17175     while (*q && *q != '=') q++;
17176     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17177     SendToProgram(buf, cps);
17178     p = q;
17179     if (*p == '=') {
17180       p++;
17181       if (*p == '\"') {
17182         p++;
17183         while (*p && *p != '\"') p++;
17184         if (*p == '\"') p++;
17185       } else {
17186         while (*p && *p != ' ') p++;
17187       }
17188     }
17189   }
17190
17191 }
17192
17193 void
17194 PeriodicUpdatesEvent (int newState)
17195 {
17196     if (newState == appData.periodicUpdates)
17197       return;
17198
17199     appData.periodicUpdates=newState;
17200
17201     /* Display type changes, so update it now */
17202 //    DisplayAnalysis();
17203
17204     /* Get the ball rolling again... */
17205     if (newState) {
17206         AnalysisPeriodicEvent(1);
17207         StartAnalysisClock();
17208     }
17209 }
17210
17211 void
17212 PonderNextMoveEvent (int newState)
17213 {
17214     if (newState == appData.ponderNextMove) return;
17215     if (gameMode == EditPosition) EditPositionDone(TRUE);
17216     if (newState) {
17217         SendToProgram("hard\n", &first);
17218         if (gameMode == TwoMachinesPlay) {
17219             SendToProgram("hard\n", &second);
17220         }
17221     } else {
17222         SendToProgram("easy\n", &first);
17223         thinkOutput[0] = NULLCHAR;
17224         if (gameMode == TwoMachinesPlay) {
17225             SendToProgram("easy\n", &second);
17226         }
17227     }
17228     appData.ponderNextMove = newState;
17229 }
17230
17231 void
17232 NewSettingEvent (int option, int *feature, char *command, int value)
17233 {
17234     char buf[MSG_SIZ];
17235
17236     if (gameMode == EditPosition) EditPositionDone(TRUE);
17237     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17238     if(feature == NULL || *feature) SendToProgram(buf, &first);
17239     if (gameMode == TwoMachinesPlay) {
17240         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17241     }
17242 }
17243
17244 void
17245 ShowThinkingEvent ()
17246 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17247 {
17248     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17249     int newState = appData.showThinking
17250         // [HGM] thinking: other features now need thinking output as well
17251         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17252
17253     if (oldState == newState) return;
17254     oldState = newState;
17255     if (gameMode == EditPosition) EditPositionDone(TRUE);
17256     if (oldState) {
17257         SendToProgram("post\n", &first);
17258         if (gameMode == TwoMachinesPlay) {
17259             SendToProgram("post\n", &second);
17260         }
17261     } else {
17262         SendToProgram("nopost\n", &first);
17263         thinkOutput[0] = NULLCHAR;
17264         if (gameMode == TwoMachinesPlay) {
17265             SendToProgram("nopost\n", &second);
17266         }
17267     }
17268 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17269 }
17270
17271 void
17272 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17273 {
17274   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17275   if (pr == NoProc) return;
17276   AskQuestion(title, question, replyPrefix, pr);
17277 }
17278
17279 void
17280 TypeInEvent (char firstChar)
17281 {
17282     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17283         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17284         gameMode == AnalyzeMode || gameMode == EditGame ||
17285         gameMode == EditPosition || gameMode == IcsExamining ||
17286         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17287         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17288                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17289                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17290         gameMode == Training) PopUpMoveDialog(firstChar);
17291 }
17292
17293 void
17294 TypeInDoneEvent (char *move)
17295 {
17296         Board board;
17297         int n, fromX, fromY, toX, toY;
17298         char promoChar;
17299         ChessMove moveType;
17300
17301         // [HGM] FENedit
17302         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17303                 EditPositionPasteFEN(move);
17304                 return;
17305         }
17306         // [HGM] movenum: allow move number to be typed in any mode
17307         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17308           ToNrEvent(2*n-1);
17309           return;
17310         }
17311         // undocumented kludge: allow command-line option to be typed in!
17312         // (potentially fatal, and does not implement the effect of the option.)
17313         // should only be used for options that are values on which future decisions will be made,
17314         // and definitely not on options that would be used during initialization.
17315         if(strstr(move, "!!! -") == move) {
17316             ParseArgsFromString(move+4);
17317             return;
17318         }
17319
17320       if (gameMode != EditGame && currentMove != forwardMostMove &&
17321         gameMode != Training) {
17322         DisplayMoveError(_("Displayed move is not current"));
17323       } else {
17324         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17325           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17326         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17327         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17328           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17329           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17330         } else {
17331           DisplayMoveError(_("Could not parse move"));
17332         }
17333       }
17334 }
17335
17336 void
17337 DisplayMove (int moveNumber)
17338 {
17339     char message[MSG_SIZ];
17340     char res[MSG_SIZ];
17341     char cpThinkOutput[MSG_SIZ];
17342
17343     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17344
17345     if (moveNumber == forwardMostMove - 1 ||
17346         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17347
17348         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17349
17350         if (strchr(cpThinkOutput, '\n')) {
17351             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17352         }
17353     } else {
17354         *cpThinkOutput = NULLCHAR;
17355     }
17356
17357     /* [AS] Hide thinking from human user */
17358     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17359         *cpThinkOutput = NULLCHAR;
17360         if( thinkOutput[0] != NULLCHAR ) {
17361             int i;
17362
17363             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17364                 cpThinkOutput[i] = '.';
17365             }
17366             cpThinkOutput[i] = NULLCHAR;
17367             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17368         }
17369     }
17370
17371     if (moveNumber == forwardMostMove - 1 &&
17372         gameInfo.resultDetails != NULL) {
17373         if (gameInfo.resultDetails[0] == NULLCHAR) {
17374           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17375         } else {
17376           snprintf(res, MSG_SIZ, " {%s} %s",
17377                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17378         }
17379     } else {
17380         res[0] = NULLCHAR;
17381     }
17382
17383     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17384         DisplayMessage(res, cpThinkOutput);
17385     } else {
17386       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17387                 WhiteOnMove(moveNumber) ? " " : ".. ",
17388                 parseList[moveNumber], res);
17389         DisplayMessage(message, cpThinkOutput);
17390     }
17391 }
17392
17393 void
17394 DisplayComment (int moveNumber, char *text)
17395 {
17396     char title[MSG_SIZ];
17397
17398     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17399       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17400     } else {
17401       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17402               WhiteOnMove(moveNumber) ? " " : ".. ",
17403               parseList[moveNumber]);
17404     }
17405     if (text != NULL && (appData.autoDisplayComment || commentUp))
17406         CommentPopUp(title, text);
17407 }
17408
17409 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17410  * might be busy thinking or pondering.  It can be omitted if your
17411  * gnuchess is configured to stop thinking immediately on any user
17412  * input.  However, that gnuchess feature depends on the FIONREAD
17413  * ioctl, which does not work properly on some flavors of Unix.
17414  */
17415 void
17416 Attention (ChessProgramState *cps)
17417 {
17418 #if ATTENTION
17419     if (!cps->useSigint) return;
17420     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17421     switch (gameMode) {
17422       case MachinePlaysWhite:
17423       case MachinePlaysBlack:
17424       case TwoMachinesPlay:
17425       case IcsPlayingWhite:
17426       case IcsPlayingBlack:
17427       case AnalyzeMode:
17428       case AnalyzeFile:
17429         /* Skip if we know it isn't thinking */
17430         if (!cps->maybeThinking) return;
17431         if (appData.debugMode)
17432           fprintf(debugFP, "Interrupting %s\n", cps->which);
17433         InterruptChildProcess(cps->pr);
17434         cps->maybeThinking = FALSE;
17435         break;
17436       default:
17437         break;
17438     }
17439 #endif /*ATTENTION*/
17440 }
17441
17442 int
17443 CheckFlags ()
17444 {
17445     if (whiteTimeRemaining <= 0) {
17446         if (!whiteFlag) {
17447             whiteFlag = TRUE;
17448             if (appData.icsActive) {
17449                 if (appData.autoCallFlag &&
17450                     gameMode == IcsPlayingBlack && !blackFlag) {
17451                   SendToICS(ics_prefix);
17452                   SendToICS("flag\n");
17453                 }
17454             } else {
17455                 if (blackFlag) {
17456                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17457                 } else {
17458                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17459                     if (appData.autoCallFlag) {
17460                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17461                         return TRUE;
17462                     }
17463                 }
17464             }
17465         }
17466     }
17467     if (blackTimeRemaining <= 0) {
17468         if (!blackFlag) {
17469             blackFlag = TRUE;
17470             if (appData.icsActive) {
17471                 if (appData.autoCallFlag &&
17472                     gameMode == IcsPlayingWhite && !whiteFlag) {
17473                   SendToICS(ics_prefix);
17474                   SendToICS("flag\n");
17475                 }
17476             } else {
17477                 if (whiteFlag) {
17478                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17479                 } else {
17480                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17481                     if (appData.autoCallFlag) {
17482                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17483                         return TRUE;
17484                     }
17485                 }
17486             }
17487         }
17488     }
17489     return FALSE;
17490 }
17491
17492 void
17493 CheckTimeControl ()
17494 {
17495     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17496         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17497
17498     /*
17499      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17500      */
17501     if ( !WhiteOnMove(forwardMostMove) ) {
17502         /* White made time control */
17503         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17504         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17505         /* [HGM] time odds: correct new time quota for time odds! */
17506                                             / WhitePlayer()->timeOdds;
17507         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17508     } else {
17509         lastBlack -= blackTimeRemaining;
17510         /* Black made time control */
17511         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17512                                             / WhitePlayer()->other->timeOdds;
17513         lastWhite = whiteTimeRemaining;
17514     }
17515 }
17516
17517 void
17518 DisplayBothClocks ()
17519 {
17520     int wom = gameMode == EditPosition ?
17521       !blackPlaysFirst : WhiteOnMove(currentMove);
17522     DisplayWhiteClock(whiteTimeRemaining, wom);
17523     DisplayBlackClock(blackTimeRemaining, !wom);
17524 }
17525
17526
17527 /* Timekeeping seems to be a portability nightmare.  I think everyone
17528    has ftime(), but I'm really not sure, so I'm including some ifdefs
17529    to use other calls if you don't.  Clocks will be less accurate if
17530    you have neither ftime nor gettimeofday.
17531 */
17532
17533 /* VS 2008 requires the #include outside of the function */
17534 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17535 #include <sys/timeb.h>
17536 #endif
17537
17538 /* Get the current time as a TimeMark */
17539 void
17540 GetTimeMark (TimeMark *tm)
17541 {
17542 #if HAVE_GETTIMEOFDAY
17543
17544     struct timeval timeVal;
17545     struct timezone timeZone;
17546
17547     gettimeofday(&timeVal, &timeZone);
17548     tm->sec = (long) timeVal.tv_sec;
17549     tm->ms = (int) (timeVal.tv_usec / 1000L);
17550
17551 #else /*!HAVE_GETTIMEOFDAY*/
17552 #if HAVE_FTIME
17553
17554 // include <sys/timeb.h> / moved to just above start of function
17555     struct timeb timeB;
17556
17557     ftime(&timeB);
17558     tm->sec = (long) timeB.time;
17559     tm->ms = (int) timeB.millitm;
17560
17561 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17562     tm->sec = (long) time(NULL);
17563     tm->ms = 0;
17564 #endif
17565 #endif
17566 }
17567
17568 /* Return the difference in milliseconds between two
17569    time marks.  We assume the difference will fit in a long!
17570 */
17571 long
17572 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17573 {
17574     return 1000L*(tm2->sec - tm1->sec) +
17575            (long) (tm2->ms - tm1->ms);
17576 }
17577
17578
17579 /*
17580  * Code to manage the game clocks.
17581  *
17582  * In tournament play, black starts the clock and then white makes a move.
17583  * We give the human user a slight advantage if he is playing white---the
17584  * clocks don't run until he makes his first move, so it takes zero time.
17585  * Also, we don't account for network lag, so we could get out of sync
17586  * with GNU Chess's clock -- but then, referees are always right.
17587  */
17588
17589 static TimeMark tickStartTM;
17590 static long intendedTickLength;
17591
17592 long
17593 NextTickLength (long timeRemaining)
17594 {
17595     long nominalTickLength, nextTickLength;
17596
17597     if (timeRemaining > 0L && timeRemaining <= 10000L)
17598       nominalTickLength = 100L;
17599     else
17600       nominalTickLength = 1000L;
17601     nextTickLength = timeRemaining % nominalTickLength;
17602     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17603
17604     return nextTickLength;
17605 }
17606
17607 /* Adjust clock one minute up or down */
17608 void
17609 AdjustClock (Boolean which, int dir)
17610 {
17611     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17612     if(which) blackTimeRemaining += 60000*dir;
17613     else      whiteTimeRemaining += 60000*dir;
17614     DisplayBothClocks();
17615     adjustedClock = TRUE;
17616 }
17617
17618 /* Stop clocks and reset to a fresh time control */
17619 void
17620 ResetClocks ()
17621 {
17622     (void) StopClockTimer();
17623     if (appData.icsActive) {
17624         whiteTimeRemaining = blackTimeRemaining = 0;
17625     } else if (searchTime) {
17626         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17627         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17628     } else { /* [HGM] correct new time quote for time odds */
17629         whiteTC = blackTC = fullTimeControlString;
17630         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17631         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17632     }
17633     if (whiteFlag || blackFlag) {
17634         DisplayTitle("");
17635         whiteFlag = blackFlag = FALSE;
17636     }
17637     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17638     DisplayBothClocks();
17639     adjustedClock = FALSE;
17640 }
17641
17642 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17643
17644 /* Decrement running clock by amount of time that has passed */
17645 void
17646 DecrementClocks ()
17647 {
17648     long timeRemaining;
17649     long lastTickLength, fudge;
17650     TimeMark now;
17651
17652     if (!appData.clockMode) return;
17653     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17654
17655     GetTimeMark(&now);
17656
17657     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17658
17659     /* Fudge if we woke up a little too soon */
17660     fudge = intendedTickLength - lastTickLength;
17661     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17662
17663     if (WhiteOnMove(forwardMostMove)) {
17664         if(whiteNPS >= 0) lastTickLength = 0;
17665         timeRemaining = whiteTimeRemaining -= lastTickLength;
17666         if(timeRemaining < 0 && !appData.icsActive) {
17667             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17668             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17669                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17670                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17671             }
17672         }
17673         DisplayWhiteClock(whiteTimeRemaining - fudge,
17674                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17675     } else {
17676         if(blackNPS >= 0) lastTickLength = 0;
17677         timeRemaining = blackTimeRemaining -= lastTickLength;
17678         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17679             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17680             if(suddenDeath) {
17681                 blackStartMove = forwardMostMove;
17682                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17683             }
17684         }
17685         DisplayBlackClock(blackTimeRemaining - fudge,
17686                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17687     }
17688     if (CheckFlags()) return;
17689
17690     if(twoBoards) { // count down secondary board's clocks as well
17691         activePartnerTime -= lastTickLength;
17692         partnerUp = 1;
17693         if(activePartner == 'W')
17694             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17695         else
17696             DisplayBlackClock(activePartnerTime, TRUE);
17697         partnerUp = 0;
17698     }
17699
17700     tickStartTM = now;
17701     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17702     StartClockTimer(intendedTickLength);
17703
17704     /* if the time remaining has fallen below the alarm threshold, sound the
17705      * alarm. if the alarm has sounded and (due to a takeback or time control
17706      * with increment) the time remaining has increased to a level above the
17707      * threshold, reset the alarm so it can sound again.
17708      */
17709
17710     if (appData.icsActive && appData.icsAlarm) {
17711
17712         /* make sure we are dealing with the user's clock */
17713         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17714                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17715            )) return;
17716
17717         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17718             alarmSounded = FALSE;
17719         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17720             PlayAlarmSound();
17721             alarmSounded = TRUE;
17722         }
17723     }
17724 }
17725
17726
17727 /* A player has just moved, so stop the previously running
17728    clock and (if in clock mode) start the other one.
17729    We redisplay both clocks in case we're in ICS mode, because
17730    ICS gives us an update to both clocks after every move.
17731    Note that this routine is called *after* forwardMostMove
17732    is updated, so the last fractional tick must be subtracted
17733    from the color that is *not* on move now.
17734 */
17735 void
17736 SwitchClocks (int newMoveNr)
17737 {
17738     long lastTickLength;
17739     TimeMark now;
17740     int flagged = FALSE;
17741
17742     GetTimeMark(&now);
17743
17744     if (StopClockTimer() && appData.clockMode) {
17745         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17746         if (!WhiteOnMove(forwardMostMove)) {
17747             if(blackNPS >= 0) lastTickLength = 0;
17748             blackTimeRemaining -= lastTickLength;
17749            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17750 //         if(pvInfoList[forwardMostMove].time == -1)
17751                  pvInfoList[forwardMostMove].time =               // use GUI time
17752                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17753         } else {
17754            if(whiteNPS >= 0) lastTickLength = 0;
17755            whiteTimeRemaining -= lastTickLength;
17756            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17757 //         if(pvInfoList[forwardMostMove].time == -1)
17758                  pvInfoList[forwardMostMove].time =
17759                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17760         }
17761         flagged = CheckFlags();
17762     }
17763     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17764     CheckTimeControl();
17765
17766     if (flagged || !appData.clockMode) return;
17767
17768     switch (gameMode) {
17769       case MachinePlaysBlack:
17770       case MachinePlaysWhite:
17771       case BeginningOfGame:
17772         if (pausing) return;
17773         break;
17774
17775       case EditGame:
17776       case PlayFromGameFile:
17777       case IcsExamining:
17778         return;
17779
17780       default:
17781         break;
17782     }
17783
17784     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17785         if(WhiteOnMove(forwardMostMove))
17786              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17787         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17788     }
17789
17790     tickStartTM = now;
17791     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17792       whiteTimeRemaining : blackTimeRemaining);
17793     StartClockTimer(intendedTickLength);
17794 }
17795
17796
17797 /* Stop both clocks */
17798 void
17799 StopClocks ()
17800 {
17801     long lastTickLength;
17802     TimeMark now;
17803
17804     if (!StopClockTimer()) return;
17805     if (!appData.clockMode) return;
17806
17807     GetTimeMark(&now);
17808
17809     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17810     if (WhiteOnMove(forwardMostMove)) {
17811         if(whiteNPS >= 0) lastTickLength = 0;
17812         whiteTimeRemaining -= lastTickLength;
17813         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17814     } else {
17815         if(blackNPS >= 0) lastTickLength = 0;
17816         blackTimeRemaining -= lastTickLength;
17817         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17818     }
17819     CheckFlags();
17820 }
17821
17822 /* Start clock of player on move.  Time may have been reset, so
17823    if clock is already running, stop and restart it. */
17824 void
17825 StartClocks ()
17826 {
17827     (void) StopClockTimer(); /* in case it was running already */
17828     DisplayBothClocks();
17829     if (CheckFlags()) return;
17830
17831     if (!appData.clockMode) return;
17832     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17833
17834     GetTimeMark(&tickStartTM);
17835     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17836       whiteTimeRemaining : blackTimeRemaining);
17837
17838    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17839     whiteNPS = blackNPS = -1;
17840     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17841        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17842         whiteNPS = first.nps;
17843     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17844        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17845         blackNPS = first.nps;
17846     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17847         whiteNPS = second.nps;
17848     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17849         blackNPS = second.nps;
17850     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17851
17852     StartClockTimer(intendedTickLength);
17853 }
17854
17855 char *
17856 TimeString (long ms)
17857 {
17858     long second, minute, hour, day;
17859     char *sign = "";
17860     static char buf[32];
17861
17862     if (ms > 0 && ms <= 9900) {
17863       /* convert milliseconds to tenths, rounding up */
17864       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17865
17866       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17867       return buf;
17868     }
17869
17870     /* convert milliseconds to seconds, rounding up */
17871     /* use floating point to avoid strangeness of integer division
17872        with negative dividends on many machines */
17873     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17874
17875     if (second < 0) {
17876         sign = "-";
17877         second = -second;
17878     }
17879
17880     day = second / (60 * 60 * 24);
17881     second = second % (60 * 60 * 24);
17882     hour = second / (60 * 60);
17883     second = second % (60 * 60);
17884     minute = second / 60;
17885     second = second % 60;
17886
17887     if (day > 0)
17888       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17889               sign, day, hour, minute, second);
17890     else if (hour > 0)
17891       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17892     else
17893       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17894
17895     return buf;
17896 }
17897
17898
17899 /*
17900  * This is necessary because some C libraries aren't ANSI C compliant yet.
17901  */
17902 char *
17903 StrStr (char *string, char *match)
17904 {
17905     int i, length;
17906
17907     length = strlen(match);
17908
17909     for (i = strlen(string) - length; i >= 0; i--, string++)
17910       if (!strncmp(match, string, length))
17911         return string;
17912
17913     return NULL;
17914 }
17915
17916 char *
17917 StrCaseStr (char *string, char *match)
17918 {
17919     int i, j, length;
17920
17921     length = strlen(match);
17922
17923     for (i = strlen(string) - length; i >= 0; i--, string++) {
17924         for (j = 0; j < length; j++) {
17925             if (ToLower(match[j]) != ToLower(string[j]))
17926               break;
17927         }
17928         if (j == length) return string;
17929     }
17930
17931     return NULL;
17932 }
17933
17934 #ifndef _amigados
17935 int
17936 StrCaseCmp (char *s1, char *s2)
17937 {
17938     char c1, c2;
17939
17940     for (;;) {
17941         c1 = ToLower(*s1++);
17942         c2 = ToLower(*s2++);
17943         if (c1 > c2) return 1;
17944         if (c1 < c2) return -1;
17945         if (c1 == NULLCHAR) return 0;
17946     }
17947 }
17948
17949
17950 int
17951 ToLower (int c)
17952 {
17953     return isupper(c) ? tolower(c) : c;
17954 }
17955
17956
17957 int
17958 ToUpper (int c)
17959 {
17960     return islower(c) ? toupper(c) : c;
17961 }
17962 #endif /* !_amigados    */
17963
17964 char *
17965 StrSave (char *s)
17966 {
17967   char *ret;
17968
17969   if ((ret = (char *) malloc(strlen(s) + 1)))
17970     {
17971       safeStrCpy(ret, s, strlen(s)+1);
17972     }
17973   return ret;
17974 }
17975
17976 char *
17977 StrSavePtr (char *s, char **savePtr)
17978 {
17979     if (*savePtr) {
17980         free(*savePtr);
17981     }
17982     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17983       safeStrCpy(*savePtr, s, strlen(s)+1);
17984     }
17985     return(*savePtr);
17986 }
17987
17988 char *
17989 PGNDate ()
17990 {
17991     time_t clock;
17992     struct tm *tm;
17993     char buf[MSG_SIZ];
17994
17995     clock = time((time_t *)NULL);
17996     tm = localtime(&clock);
17997     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17998             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17999     return StrSave(buf);
18000 }
18001
18002
18003 char *
18004 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18005 {
18006     int i, j, fromX, fromY, toX, toY;
18007     int whiteToPlay, haveRights = nrCastlingRights;
18008     char buf[MSG_SIZ];
18009     char *p, *q;
18010     int emptycount;
18011     ChessSquare piece;
18012
18013     whiteToPlay = (gameMode == EditPosition) ?
18014       !blackPlaysFirst : (move % 2 == 0);
18015     p = buf;
18016
18017     /* Piece placement data */
18018     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18019         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18020         emptycount = 0;
18021         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18022             if (boards[move][i][j] == EmptySquare) {
18023                 emptycount++;
18024             } else { ChessSquare piece = boards[move][i][j];
18025                 if (emptycount > 0) {
18026                     if(emptycount<10) /* [HGM] can be >= 10 */
18027                         *p++ = '0' + emptycount;
18028                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18029                     emptycount = 0;
18030                 }
18031                 if(PieceToChar(piece) == '+') {
18032                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18033                     *p++ = '+';
18034                     piece = (ChessSquare)(CHUDEMOTED(piece));
18035                 }
18036                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18037                 if(*p = PieceSuffix(piece)) p++;
18038                 if(p[-1] == '~') {
18039                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18040                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18041                     *p++ = '~';
18042                 }
18043             }
18044         }
18045         if (emptycount > 0) {
18046             if(emptycount<10) /* [HGM] can be >= 10 */
18047                 *p++ = '0' + emptycount;
18048             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18049             emptycount = 0;
18050         }
18051         *p++ = '/';
18052     }
18053     *(p - 1) = ' ';
18054
18055     /* [HGM] print Crazyhouse or Shogi holdings */
18056     if( gameInfo.holdingsWidth ) {
18057         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18058         q = p;
18059         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18060             piece = boards[move][i][BOARD_WIDTH-1];
18061             if( piece != EmptySquare )
18062               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18063                   *p++ = PieceToChar(piece);
18064         }
18065         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18066             piece = boards[move][BOARD_HEIGHT-i-1][0];
18067             if( piece != EmptySquare )
18068               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18069                   *p++ = PieceToChar(piece);
18070         }
18071
18072         if( q == p ) *p++ = '-';
18073         *p++ = ']';
18074         *p++ = ' ';
18075     }
18076
18077     /* Active color */
18078     *p++ = whiteToPlay ? 'w' : 'b';
18079     *p++ = ' ';
18080
18081   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18082     haveRights = 0; q = p;
18083     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18084       piece = boards[move][0][i];
18085       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18086         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18087       }
18088     }
18089     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18090       piece = boards[move][BOARD_HEIGHT-1][i];
18091       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18092         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18093       }
18094     }
18095     if(p == q) *p++ = '-';
18096     *p++ = ' ';
18097   }
18098
18099   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18100     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18101   } else {
18102   if(haveRights) {
18103      int handW=0, handB=0;
18104      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18105         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18106         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18107      }
18108      q = p;
18109      if(appData.fischerCastling) {
18110         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18111            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18112                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18113         } else {
18114        /* [HGM] write directly from rights */
18115            if(boards[move][CASTLING][2] != NoRights &&
18116               boards[move][CASTLING][0] != NoRights   )
18117                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18118            if(boards[move][CASTLING][2] != NoRights &&
18119               boards[move][CASTLING][1] != NoRights   )
18120                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18121         }
18122         if(handB) {
18123            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18124                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18125         } else {
18126            if(boards[move][CASTLING][5] != NoRights &&
18127               boards[move][CASTLING][3] != NoRights   )
18128                 *p++ = boards[move][CASTLING][3] + AAA;
18129            if(boards[move][CASTLING][5] != NoRights &&
18130               boards[move][CASTLING][4] != NoRights   )
18131                 *p++ = boards[move][CASTLING][4] + AAA;
18132         }
18133      } else {
18134
18135         /* [HGM] write true castling rights */
18136         if( nrCastlingRights == 6 ) {
18137             int q, k=0;
18138             if(boards[move][CASTLING][0] != NoRights &&
18139                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18140             q = (boards[move][CASTLING][1] != NoRights &&
18141                  boards[move][CASTLING][2] != NoRights  );
18142             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18143                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18144                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18145                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18146             }
18147             if(q) *p++ = 'Q';
18148             k = 0;
18149             if(boards[move][CASTLING][3] != NoRights &&
18150                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18151             q = (boards[move][CASTLING][4] != NoRights &&
18152                  boards[move][CASTLING][5] != NoRights  );
18153             if(handB) {
18154                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18155                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18156                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18157             }
18158             if(q) *p++ = 'q';
18159         }
18160      }
18161      if (q == p) *p++ = '-'; /* No castling rights */
18162      *p++ = ' ';
18163   }
18164
18165   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18166      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18167      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18168     /* En passant target square */
18169     if (move > backwardMostMove) {
18170         fromX = moveList[move - 1][0] - AAA;
18171         fromY = moveList[move - 1][1] - ONE;
18172         toX = moveList[move - 1][2] - AAA;
18173         toY = moveList[move - 1][3] - ONE;
18174         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18175             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18176             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18177             fromX == toX) {
18178             /* 2-square pawn move just happened */
18179             *p++ = toX + AAA;
18180             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18181         } else {
18182             *p++ = '-';
18183         }
18184     } else if(move == backwardMostMove) {
18185         // [HGM] perhaps we should always do it like this, and forget the above?
18186         if((signed char)boards[move][EP_STATUS] >= 0) {
18187             *p++ = boards[move][EP_STATUS] + AAA;
18188             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18189         } else {
18190             *p++ = '-';
18191         }
18192     } else {
18193         *p++ = '-';
18194     }
18195     *p++ = ' ';
18196   }
18197   }
18198
18199     if(moveCounts)
18200     {   int i = 0, j=move;
18201
18202         /* [HGM] find reversible plies */
18203         if (appData.debugMode) { int k;
18204             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18205             for(k=backwardMostMove; k<=forwardMostMove; k++)
18206                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18207
18208         }
18209
18210         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18211         if( j == backwardMostMove ) i += initialRulePlies;
18212         sprintf(p, "%d ", i);
18213         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18214
18215         /* Fullmove number */
18216         sprintf(p, "%d", (move / 2) + 1);
18217     } else *--p = NULLCHAR;
18218
18219     return StrSave(buf);
18220 }
18221
18222 Boolean
18223 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18224 {
18225     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18226     char *p, c;
18227     int emptycount, virgin[BOARD_FILES];
18228     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18229
18230     p = fen;
18231
18232     /* Piece placement data */
18233     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18234         j = 0;
18235         for (;;) {
18236             if (*p == '/' || *p == ' ' || *p == '[' ) {
18237                 if(j > w) w = j;
18238                 emptycount = gameInfo.boardWidth - j;
18239                 while (emptycount--)
18240                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18241                 if (*p == '/') p++;
18242                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18243                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18244                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18245                     }
18246                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18247                 }
18248                 break;
18249 #if(BOARD_FILES >= 10)*0
18250             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18251                 p++; emptycount=10;
18252                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18253                 while (emptycount--)
18254                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18255 #endif
18256             } else if (*p == '*') {
18257                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18258             } else if (isdigit(*p)) {
18259                 emptycount = *p++ - '0';
18260                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18261                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18262                 while (emptycount--)
18263                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18264             } else if (*p == '<') {
18265                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18266                 else if (i != 0 || !shuffle) return FALSE;
18267                 p++;
18268             } else if (shuffle && *p == '>') {
18269                 p++; // for now ignore closing shuffle range, and assume rank-end
18270             } else if (*p == '?') {
18271                 if (j >= gameInfo.boardWidth) return FALSE;
18272                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18273                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18274             } else if (*p == '+' || isalpha(*p)) {
18275                 char *q, *s = SUFFIXES;
18276                 if (j >= gameInfo.boardWidth) return FALSE;
18277                 if(*p=='+') {
18278                     char c = *++p;
18279                     if(q = strchr(s, p[1])) p++;
18280                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18281                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18282                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18283                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18284                 } else {
18285                     char c = *p++;
18286                     if(q = strchr(s, *p)) p++;
18287                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18288                 }
18289
18290                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18291                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18292                     piece = (ChessSquare) (PROMOTED(piece));
18293                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18294                     p++;
18295                 }
18296                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18297                 if(piece == king) wKingRank = i;
18298                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18299             } else {
18300                 return FALSE;
18301             }
18302         }
18303     }
18304     while (*p == '/' || *p == ' ') p++;
18305
18306     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18307
18308     /* [HGM] by default clear Crazyhouse holdings, if present */
18309     if(gameInfo.holdingsWidth) {
18310        for(i=0; i<BOARD_HEIGHT; i++) {
18311            board[i][0]             = EmptySquare; /* black holdings */
18312            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18313            board[i][1]             = (ChessSquare) 0; /* black counts */
18314            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18315        }
18316     }
18317
18318     /* [HGM] look for Crazyhouse holdings here */
18319     while(*p==' ') p++;
18320     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18321         int swap=0, wcnt=0, bcnt=0;
18322         if(*p == '[') p++;
18323         if(*p == '<') swap++, p++;
18324         if(*p == '-' ) p++; /* empty holdings */ else {
18325             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18326             /* if we would allow FEN reading to set board size, we would   */
18327             /* have to add holdings and shift the board read so far here   */
18328             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18329                 p++;
18330                 if((int) piece >= (int) BlackPawn ) {
18331                     i = (int)piece - (int)BlackPawn;
18332                     i = PieceToNumber((ChessSquare)i);
18333                     if( i >= gameInfo.holdingsSize ) return FALSE;
18334                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18335                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18336                     bcnt++;
18337                 } else {
18338                     i = (int)piece - (int)WhitePawn;
18339                     i = PieceToNumber((ChessSquare)i);
18340                     if( i >= gameInfo.holdingsSize ) return FALSE;
18341                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18342                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18343                     wcnt++;
18344                 }
18345             }
18346             if(subst) { // substitute back-rank question marks by holdings pieces
18347                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18348                     int k, m, n = bcnt + 1;
18349                     if(board[0][j] == ClearBoard) {
18350                         if(!wcnt) return FALSE;
18351                         n = rand() % wcnt;
18352                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18353                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18354                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18355                             break;
18356                         }
18357                     }
18358                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18359                         if(!bcnt) return FALSE;
18360                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18361                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18362                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18363                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18364                             break;
18365                         }
18366                     }
18367                 }
18368                 subst = 0;
18369             }
18370         }
18371         if(*p == ']') p++;
18372     }
18373
18374     if(subst) return FALSE; // substitution requested, but no holdings
18375
18376     while(*p == ' ') p++;
18377
18378     /* Active color */
18379     c = *p++;
18380     if(appData.colorNickNames) {
18381       if( c == appData.colorNickNames[0] ) c = 'w'; else
18382       if( c == appData.colorNickNames[1] ) c = 'b';
18383     }
18384     switch (c) {
18385       case 'w':
18386         *blackPlaysFirst = FALSE;
18387         break;
18388       case 'b':
18389         *blackPlaysFirst = TRUE;
18390         break;
18391       default:
18392         return FALSE;
18393     }
18394
18395     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18396     /* return the extra info in global variiables             */
18397
18398     while(*p==' ') p++;
18399
18400     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18401         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18402         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18403     }
18404
18405     /* set defaults in case FEN is incomplete */
18406     board[EP_STATUS] = EP_UNKNOWN;
18407     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18408     for(i=0; i<nrCastlingRights; i++ ) {
18409         board[CASTLING][i] =
18410             appData.fischerCastling ? NoRights : initialRights[i];
18411     }   /* assume possible unless obviously impossible */
18412     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18413     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18414     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18415                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18416     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18417     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18418     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18419                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18420     FENrulePlies = 0;
18421
18422     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18423       char *q = p;
18424       int w=0, b=0;
18425       while(isalpha(*p)) {
18426         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18427         if(islower(*p)) b |= 1 << (*p++ - 'a');
18428       }
18429       if(*p == '-') p++;
18430       if(p != q) {
18431         board[TOUCHED_W] = ~w;
18432         board[TOUCHED_B] = ~b;
18433         while(*p == ' ') p++;
18434       }
18435     } else
18436
18437     if(nrCastlingRights) {
18438       int fischer = 0;
18439       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18440       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18441           /* castling indicator present, so default becomes no castlings */
18442           for(i=0; i<nrCastlingRights; i++ ) {
18443                  board[CASTLING][i] = NoRights;
18444           }
18445       }
18446       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18447              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18448              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18449              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18450         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18451
18452         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18453             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18454             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18455         }
18456         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18457             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18458         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18459                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18460         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18461                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18462         switch(c) {
18463           case'K':
18464               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18465               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18466               board[CASTLING][2] = whiteKingFile;
18467               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18468               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18469               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18470               break;
18471           case'Q':
18472               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18473               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18474               board[CASTLING][2] = whiteKingFile;
18475               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18476               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18477               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18478               break;
18479           case'k':
18480               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18481               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18482               board[CASTLING][5] = blackKingFile;
18483               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18484               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18485               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18486               break;
18487           case'q':
18488               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18489               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18490               board[CASTLING][5] = blackKingFile;
18491               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18492               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18493               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18494           case '-':
18495               break;
18496           default: /* FRC castlings */
18497               if(c >= 'a') { /* black rights */
18498                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18499                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18500                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18501                   if(i == BOARD_RGHT) break;
18502                   board[CASTLING][5] = i;
18503                   c -= AAA;
18504                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18505                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18506                   if(c > i)
18507                       board[CASTLING][3] = c;
18508                   else
18509                       board[CASTLING][4] = c;
18510               } else { /* white rights */
18511                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18512                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18513                     if(board[0][i] == WhiteKing) break;
18514                   if(i == BOARD_RGHT) break;
18515                   board[CASTLING][2] = i;
18516                   c -= AAA - 'a' + 'A';
18517                   if(board[0][c] >= WhiteKing) break;
18518                   if(c > i)
18519                       board[CASTLING][0] = c;
18520                   else
18521                       board[CASTLING][1] = c;
18522               }
18523         }
18524       }
18525       for(i=0; i<nrCastlingRights; i++)
18526         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18527       if(gameInfo.variant == VariantSChess)
18528         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18529       if(fischer && shuffle) appData.fischerCastling = TRUE;
18530     if (appData.debugMode) {
18531         fprintf(debugFP, "FEN castling rights:");
18532         for(i=0; i<nrCastlingRights; i++)
18533         fprintf(debugFP, " %d", board[CASTLING][i]);
18534         fprintf(debugFP, "\n");
18535     }
18536
18537       while(*p==' ') p++;
18538     }
18539
18540     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18541
18542     /* read e.p. field in games that know e.p. capture */
18543     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18544        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18545        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18546       if(*p=='-') {
18547         p++; board[EP_STATUS] = EP_NONE;
18548       } else {
18549          char c = *p++ - AAA;
18550
18551          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18552          if(*p >= '0' && *p <='9') p++;
18553          board[EP_STATUS] = c;
18554       }
18555     }
18556
18557
18558     if(sscanf(p, "%d", &i) == 1) {
18559         FENrulePlies = i; /* 50-move ply counter */
18560         /* (The move number is still ignored)    */
18561     }
18562
18563     return TRUE;
18564 }
18565
18566 void
18567 EditPositionPasteFEN (char *fen)
18568 {
18569   if (fen != NULL) {
18570     Board initial_position;
18571
18572     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18573       DisplayError(_("Bad FEN position in clipboard"), 0);
18574       return ;
18575     } else {
18576       int savedBlackPlaysFirst = blackPlaysFirst;
18577       EditPositionEvent();
18578       blackPlaysFirst = savedBlackPlaysFirst;
18579       CopyBoard(boards[0], initial_position);
18580       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18581       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18582       DisplayBothClocks();
18583       DrawPosition(FALSE, boards[currentMove]);
18584     }
18585   }
18586 }
18587
18588 static char cseq[12] = "\\   ";
18589
18590 Boolean
18591 set_cont_sequence (char *new_seq)
18592 {
18593     int len;
18594     Boolean ret;
18595
18596     // handle bad attempts to set the sequence
18597         if (!new_seq)
18598                 return 0; // acceptable error - no debug
18599
18600     len = strlen(new_seq);
18601     ret = (len > 0) && (len < sizeof(cseq));
18602     if (ret)
18603       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18604     else if (appData.debugMode)
18605       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18606     return ret;
18607 }
18608
18609 /*
18610     reformat a source message so words don't cross the width boundary.  internal
18611     newlines are not removed.  returns the wrapped size (no null character unless
18612     included in source message).  If dest is NULL, only calculate the size required
18613     for the dest buffer.  lp argument indicats line position upon entry, and it's
18614     passed back upon exit.
18615 */
18616 int
18617 wrap (char *dest, char *src, int count, int width, int *lp)
18618 {
18619     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18620
18621     cseq_len = strlen(cseq);
18622     old_line = line = *lp;
18623     ansi = len = clen = 0;
18624
18625     for (i=0; i < count; i++)
18626     {
18627         if (src[i] == '\033')
18628             ansi = 1;
18629
18630         // if we hit the width, back up
18631         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18632         {
18633             // store i & len in case the word is too long
18634             old_i = i, old_len = len;
18635
18636             // find the end of the last word
18637             while (i && src[i] != ' ' && src[i] != '\n')
18638             {
18639                 i--;
18640                 len--;
18641             }
18642
18643             // word too long?  restore i & len before splitting it
18644             if ((old_i-i+clen) >= width)
18645             {
18646                 i = old_i;
18647                 len = old_len;
18648             }
18649
18650             // extra space?
18651             if (i && src[i-1] == ' ')
18652                 len--;
18653
18654             if (src[i] != ' ' && src[i] != '\n')
18655             {
18656                 i--;
18657                 if (len)
18658                     len--;
18659             }
18660
18661             // now append the newline and continuation sequence
18662             if (dest)
18663                 dest[len] = '\n';
18664             len++;
18665             if (dest)
18666                 strncpy(dest+len, cseq, cseq_len);
18667             len += cseq_len;
18668             line = cseq_len;
18669             clen = cseq_len;
18670             continue;
18671         }
18672
18673         if (dest)
18674             dest[len] = src[i];
18675         len++;
18676         if (!ansi)
18677             line++;
18678         if (src[i] == '\n')
18679             line = 0;
18680         if (src[i] == 'm')
18681             ansi = 0;
18682     }
18683     if (dest && appData.debugMode)
18684     {
18685         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18686             count, width, line, len, *lp);
18687         show_bytes(debugFP, src, count);
18688         fprintf(debugFP, "\ndest: ");
18689         show_bytes(debugFP, dest, len);
18690         fprintf(debugFP, "\n");
18691     }
18692     *lp = dest ? line : old_line;
18693
18694     return len;
18695 }
18696
18697 // [HGM] vari: routines for shelving variations
18698 Boolean modeRestore = FALSE;
18699
18700 void
18701 PushInner (int firstMove, int lastMove)
18702 {
18703         int i, j, nrMoves = lastMove - firstMove;
18704
18705         // push current tail of game on stack
18706         savedResult[storedGames] = gameInfo.result;
18707         savedDetails[storedGames] = gameInfo.resultDetails;
18708         gameInfo.resultDetails = NULL;
18709         savedFirst[storedGames] = firstMove;
18710         savedLast [storedGames] = lastMove;
18711         savedFramePtr[storedGames] = framePtr;
18712         framePtr -= nrMoves; // reserve space for the boards
18713         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18714             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18715             for(j=0; j<MOVE_LEN; j++)
18716                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18717             for(j=0; j<2*MOVE_LEN; j++)
18718                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18719             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18720             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18721             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18722             pvInfoList[firstMove+i-1].depth = 0;
18723             commentList[framePtr+i] = commentList[firstMove+i];
18724             commentList[firstMove+i] = NULL;
18725         }
18726
18727         storedGames++;
18728         forwardMostMove = firstMove; // truncate game so we can start variation
18729 }
18730
18731 void
18732 PushTail (int firstMove, int lastMove)
18733 {
18734         if(appData.icsActive) { // only in local mode
18735                 forwardMostMove = currentMove; // mimic old ICS behavior
18736                 return;
18737         }
18738         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18739
18740         PushInner(firstMove, lastMove);
18741         if(storedGames == 1) GreyRevert(FALSE);
18742         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18743 }
18744
18745 void
18746 PopInner (Boolean annotate)
18747 {
18748         int i, j, nrMoves;
18749         char buf[8000], moveBuf[20];
18750
18751         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18752         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18753         nrMoves = savedLast[storedGames] - currentMove;
18754         if(annotate) {
18755                 int cnt = 10;
18756                 if(!WhiteOnMove(currentMove))
18757                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18758                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18759                 for(i=currentMove; i<forwardMostMove; i++) {
18760                         if(WhiteOnMove(i))
18761                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18762                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18763                         strcat(buf, moveBuf);
18764                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18765                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18766                 }
18767                 strcat(buf, ")");
18768         }
18769         for(i=1; i<=nrMoves; i++) { // copy last variation back
18770             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18771             for(j=0; j<MOVE_LEN; j++)
18772                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18773             for(j=0; j<2*MOVE_LEN; j++)
18774                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18775             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18776             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18777             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18778             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18779             commentList[currentMove+i] = commentList[framePtr+i];
18780             commentList[framePtr+i] = NULL;
18781         }
18782         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18783         framePtr = savedFramePtr[storedGames];
18784         gameInfo.result = savedResult[storedGames];
18785         if(gameInfo.resultDetails != NULL) {
18786             free(gameInfo.resultDetails);
18787       }
18788         gameInfo.resultDetails = savedDetails[storedGames];
18789         forwardMostMove = currentMove + nrMoves;
18790 }
18791
18792 Boolean
18793 PopTail (Boolean annotate)
18794 {
18795         if(appData.icsActive) return FALSE; // only in local mode
18796         if(!storedGames) return FALSE; // sanity
18797         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18798
18799         PopInner(annotate);
18800         if(currentMove < forwardMostMove) ForwardEvent(); else
18801         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18802
18803         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18804         return TRUE;
18805 }
18806
18807 void
18808 CleanupTail ()
18809 {       // remove all shelved variations
18810         int i;
18811         for(i=0; i<storedGames; i++) {
18812             if(savedDetails[i])
18813                 free(savedDetails[i]);
18814             savedDetails[i] = NULL;
18815         }
18816         for(i=framePtr; i<MAX_MOVES; i++) {
18817                 if(commentList[i]) free(commentList[i]);
18818                 commentList[i] = NULL;
18819         }
18820         framePtr = MAX_MOVES-1;
18821         storedGames = 0;
18822 }
18823
18824 void
18825 LoadVariation (int index, char *text)
18826 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18827         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18828         int level = 0, move;
18829
18830         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18831         // first find outermost bracketing variation
18832         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18833             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18834                 if(*p == '{') wait = '}'; else
18835                 if(*p == '[') wait = ']'; else
18836                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18837                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18838             }
18839             if(*p == wait) wait = NULLCHAR; // closing ]} found
18840             p++;
18841         }
18842         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18843         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18844         end[1] = NULLCHAR; // clip off comment beyond variation
18845         ToNrEvent(currentMove-1);
18846         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18847         // kludge: use ParsePV() to append variation to game
18848         move = currentMove;
18849         ParsePV(start, TRUE, TRUE);
18850         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18851         ClearPremoveHighlights();
18852         CommentPopDown();
18853         ToNrEvent(currentMove+1);
18854 }
18855
18856 void
18857 LoadTheme ()
18858 {
18859     char *p, *q, buf[MSG_SIZ];
18860     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18861         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18862         ParseArgsFromString(buf);
18863         ActivateTheme(TRUE); // also redo colors
18864         return;
18865     }
18866     p = nickName;
18867     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18868     {
18869         int len;
18870         q = appData.themeNames;
18871         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18872       if(appData.useBitmaps) {
18873         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18874                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18875                 appData.liteBackTextureMode,
18876                 appData.darkBackTextureMode );
18877       } else {
18878         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18879                 Col2Text(2),   // lightSquareColor
18880                 Col2Text(3) ); // darkSquareColor
18881       }
18882       if(appData.useBorder) {
18883         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18884                 appData.border);
18885       } else {
18886         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18887       }
18888       if(appData.useFont) {
18889         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18890                 appData.renderPiecesWithFont,
18891                 appData.fontToPieceTable,
18892                 Col2Text(9),    // appData.fontBackColorWhite
18893                 Col2Text(10) ); // appData.fontForeColorBlack
18894       } else {
18895         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18896                 appData.pieceDirectory);
18897         if(!appData.pieceDirectory[0])
18898           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18899                 Col2Text(0),   // whitePieceColor
18900                 Col2Text(1) ); // blackPieceColor
18901       }
18902       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18903                 Col2Text(4),   // highlightSquareColor
18904                 Col2Text(5) ); // premoveHighlightColor
18905         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18906         if(insert != q) insert[-1] = NULLCHAR;
18907         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18908         if(q)   free(q);
18909     }
18910     ActivateTheme(FALSE);
18911 }