Fix setting default piece from 'choice' command
[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   *p = partner;
5399   return 1;
5400 }
5401
5402 void
5403 Sweep (int step)
5404 {
5405     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5406     static int toggleFlag;
5407     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5408     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5409     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5410     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5411     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5412     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5413     do {
5414         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5415         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5416         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5417         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5418         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5419         if(!step) step = -1;
5420     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5421             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5422             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5423             promoSweep == pawn ||
5424             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5425             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5426     if(toX >= 0) {
5427         int victim = boards[currentMove][toY][toX];
5428         boards[currentMove][toY][toX] = promoSweep;
5429         DrawPosition(FALSE, boards[currentMove]);
5430         boards[currentMove][toY][toX] = victim;
5431     } else
5432     ChangeDragPiece(promoSweep);
5433 }
5434
5435 int
5436 PromoScroll (int x, int y)
5437 {
5438   int step = 0;
5439
5440   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5441   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5442   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5443   if(!step) return FALSE;
5444   lastX = x; lastY = y;
5445   if((promoSweep < BlackPawn) == flipView) step = -step;
5446   if(step > 0) selectFlag = 1;
5447   if(!selectFlag) Sweep(step);
5448   return FALSE;
5449 }
5450
5451 void
5452 NextPiece (int step)
5453 {
5454     ChessSquare piece = boards[currentMove][toY][toX];
5455     do {
5456         pieceSweep -= step;
5457         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5458         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5459         if(!step) step = -1;
5460     } while(PieceToChar(pieceSweep) == '.');
5461     boards[currentMove][toY][toX] = pieceSweep;
5462     DrawPosition(FALSE, boards[currentMove]);
5463     boards[currentMove][toY][toX] = piece;
5464 }
5465 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5466 void
5467 AlphaRank (char *move, int n)
5468 {
5469 //    char *p = move, c; int x, y;
5470
5471     if (appData.debugMode) {
5472         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5473     }
5474
5475     if(move[1]=='*' &&
5476        move[2]>='0' && move[2]<='9' &&
5477        move[3]>='a' && move[3]<='x'    ) {
5478         move[1] = '@';
5479         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5480         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5481     } else
5482     if(move[0]>='0' && move[0]<='9' &&
5483        move[1]>='a' && move[1]<='x' &&
5484        move[2]>='0' && move[2]<='9' &&
5485        move[3]>='a' && move[3]<='x'    ) {
5486         /* input move, Shogi -> normal */
5487         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5488         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5489         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5490         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5491     } else
5492     if(move[1]=='@' &&
5493        move[3]>='0' && move[3]<='9' &&
5494        move[2]>='a' && move[2]<='x'    ) {
5495         move[1] = '*';
5496         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5497         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5498     } else
5499     if(
5500        move[0]>='a' && move[0]<='x' &&
5501        move[3]>='0' && move[3]<='9' &&
5502        move[2]>='a' && move[2]<='x'    ) {
5503          /* output move, normal -> Shogi */
5504         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5505         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5506         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5507         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5508         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5509     }
5510     if (appData.debugMode) {
5511         fprintf(debugFP, "   out = '%s'\n", move);
5512     }
5513 }
5514
5515 char yy_textstr[8000];
5516
5517 /* Parser for moves from gnuchess, ICS, or user typein box */
5518 Boolean
5519 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5520 {
5521     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5522
5523     switch (*moveType) {
5524       case WhitePromotion:
5525       case BlackPromotion:
5526       case WhiteNonPromotion:
5527       case BlackNonPromotion:
5528       case NormalMove:
5529       case FirstLeg:
5530       case WhiteCapturesEnPassant:
5531       case BlackCapturesEnPassant:
5532       case WhiteKingSideCastle:
5533       case WhiteQueenSideCastle:
5534       case BlackKingSideCastle:
5535       case BlackQueenSideCastle:
5536       case WhiteKingSideCastleWild:
5537       case WhiteQueenSideCastleWild:
5538       case BlackKingSideCastleWild:
5539       case BlackQueenSideCastleWild:
5540       /* Code added by Tord: */
5541       case WhiteHSideCastleFR:
5542       case WhiteASideCastleFR:
5543       case BlackHSideCastleFR:
5544       case BlackASideCastleFR:
5545       /* End of code added by Tord */
5546       case IllegalMove:         /* bug or odd chess variant */
5547         if(currentMoveString[1] == '@') { // illegal drop
5548           *fromX = WhiteOnMove(moveNum) ?
5549             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5550             (int) CharToPiece(ToLower(currentMoveString[0]));
5551           goto drop;
5552         }
5553         *fromX = currentMoveString[0] - AAA;
5554         *fromY = currentMoveString[1] - ONE;
5555         *toX = currentMoveString[2] - AAA;
5556         *toY = currentMoveString[3] - ONE;
5557         *promoChar = currentMoveString[4];
5558         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5559             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5560     if (appData.debugMode) {
5561         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5562     }
5563             *fromX = *fromY = *toX = *toY = 0;
5564             return FALSE;
5565         }
5566         if (appData.testLegality) {
5567           return (*moveType != IllegalMove);
5568         } else {
5569           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5570                          // [HGM] lion: if this is a double move we are less critical
5571                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5572         }
5573
5574       case WhiteDrop:
5575       case BlackDrop:
5576         *fromX = *moveType == WhiteDrop ?
5577           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5578           (int) CharToPiece(ToLower(currentMoveString[0]));
5579       drop:
5580         *fromY = DROP_RANK;
5581         *toX = currentMoveString[2] - AAA;
5582         *toY = currentMoveString[3] - ONE;
5583         *promoChar = NULLCHAR;
5584         return TRUE;
5585
5586       case AmbiguousMove:
5587       case ImpossibleMove:
5588       case EndOfFile:
5589       case ElapsedTime:
5590       case Comment:
5591       case PGNTag:
5592       case NAG:
5593       case WhiteWins:
5594       case BlackWins:
5595       case GameIsDrawn:
5596       default:
5597     if (appData.debugMode) {
5598         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5599     }
5600         /* bug? */
5601         *fromX = *fromY = *toX = *toY = 0;
5602         *promoChar = NULLCHAR;
5603         return FALSE;
5604     }
5605 }
5606
5607 Boolean pushed = FALSE;
5608 char *lastParseAttempt;
5609
5610 void
5611 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5612 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5613   int fromX, fromY, toX, toY; char promoChar;
5614   ChessMove moveType;
5615   Boolean valid;
5616   int nr = 0;
5617
5618   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5619   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5620     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5621     pushed = TRUE;
5622   }
5623   endPV = forwardMostMove;
5624   do {
5625     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5626     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5627     lastParseAttempt = pv;
5628     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5629     if(!valid && nr == 0 &&
5630        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5631         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5632         // Hande case where played move is different from leading PV move
5633         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5634         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5635         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5636         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5637           endPV += 2; // if position different, keep this
5638           moveList[endPV-1][0] = fromX + AAA;
5639           moveList[endPV-1][1] = fromY + ONE;
5640           moveList[endPV-1][2] = toX + AAA;
5641           moveList[endPV-1][3] = toY + ONE;
5642           parseList[endPV-1][0] = NULLCHAR;
5643           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5644         }
5645       }
5646     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5647     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5648     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5649     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5650         valid++; // allow comments in PV
5651         continue;
5652     }
5653     nr++;
5654     if(endPV+1 > framePtr) break; // no space, truncate
5655     if(!valid) break;
5656     endPV++;
5657     CopyBoard(boards[endPV], boards[endPV-1]);
5658     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5659     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5660     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5661     CoordsToAlgebraic(boards[endPV - 1],
5662                              PosFlags(endPV - 1),
5663                              fromY, fromX, toY, toX, promoChar,
5664                              parseList[endPV - 1]);
5665   } while(valid);
5666   if(atEnd == 2) return; // used hidden, for PV conversion
5667   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5668   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5669   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5670                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5671   DrawPosition(TRUE, boards[currentMove]);
5672 }
5673
5674 int
5675 MultiPV (ChessProgramState *cps, int kind)
5676 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5677         int i;
5678         for(i=0; i<cps->nrOptions; i++) {
5679             char *s = cps->option[i].name;
5680             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5681             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5682                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5683         }
5684         return -1;
5685 }
5686
5687 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5688 static int multi, pv_margin;
5689 static ChessProgramState *activeCps;
5690
5691 Boolean
5692 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5693 {
5694         int startPV, lineStart, origIndex = index;
5695         char *p, buf2[MSG_SIZ];
5696         ChessProgramState *cps = (pane ? &second : &first);
5697
5698         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5699         lastX = x; lastY = y;
5700         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5701         lineStart = startPV = index;
5702         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5703         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5704         index = startPV;
5705         do{ while(buf[index] && buf[index] != '\n') index++;
5706         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5707         buf[index] = 0;
5708         if(lineStart == 0 && gameMode == AnalyzeMode) {
5709             int n = 0;
5710             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5711             if(n == 0) { // click not on "fewer" or "more"
5712                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5713                     pv_margin = cps->option[multi].value;
5714                     activeCps = cps; // non-null signals margin adjustment
5715                 }
5716             } else if((multi = MultiPV(cps, 1)) >= 0) {
5717                 n += cps->option[multi].value; if(n < 1) n = 1;
5718                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5719                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5720                 cps->option[multi].value = n;
5721                 *start = *end = 0;
5722                 return FALSE;
5723             }
5724         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5725                 ExcludeClick(origIndex - lineStart);
5726                 return FALSE;
5727         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5728                 Collapse(origIndex - lineStart);
5729                 return FALSE;
5730         }
5731         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5732         *start = startPV; *end = index-1;
5733         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5734         return TRUE;
5735 }
5736
5737 char *
5738 PvToSAN (char *pv)
5739 {
5740         static char buf[10*MSG_SIZ];
5741         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5742         *buf = NULLCHAR;
5743         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5744         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5745         for(i = forwardMostMove; i<endPV; i++){
5746             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5747             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5748             k += strlen(buf+k);
5749         }
5750         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5751         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5752         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5753         endPV = savedEnd;
5754         return buf;
5755 }
5756
5757 Boolean
5758 LoadPV (int x, int y)
5759 { // called on right mouse click to load PV
5760   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5761   lastX = x; lastY = y;
5762   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5763   extendGame = FALSE;
5764   return TRUE;
5765 }
5766
5767 void
5768 UnLoadPV ()
5769 {
5770   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5771   if(activeCps) {
5772     if(pv_margin != activeCps->option[multi].value) {
5773       char buf[MSG_SIZ];
5774       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5775       SendToProgram(buf, activeCps);
5776       activeCps->option[multi].value = pv_margin;
5777     }
5778     activeCps = NULL;
5779     return;
5780   }
5781   if(endPV < 0) return;
5782   if(appData.autoCopyPV) CopyFENToClipboard();
5783   endPV = -1;
5784   if(extendGame && currentMove > forwardMostMove) {
5785         Boolean saveAnimate = appData.animate;
5786         if(pushed) {
5787             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5788                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5789             } else storedGames--; // abandon shelved tail of original game
5790         }
5791         pushed = FALSE;
5792         forwardMostMove = currentMove;
5793         currentMove = oldFMM;
5794         appData.animate = FALSE;
5795         ToNrEvent(forwardMostMove);
5796         appData.animate = saveAnimate;
5797   }
5798   currentMove = forwardMostMove;
5799   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5800   ClearPremoveHighlights();
5801   DrawPosition(TRUE, boards[currentMove]);
5802 }
5803
5804 void
5805 MovePV (int x, int y, int h)
5806 { // step through PV based on mouse coordinates (called on mouse move)
5807   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5808
5809   if(activeCps) { // adjusting engine's multi-pv margin
5810     if(x > lastX) pv_margin++; else
5811     if(x < lastX) pv_margin -= (pv_margin > 0);
5812     if(x != lastX) {
5813       char buf[MSG_SIZ];
5814       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5815       DisplayMessage(buf, "");
5816     }
5817     lastX = x;
5818     return;
5819   }
5820   // we must somehow check if right button is still down (might be released off board!)
5821   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5822   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5823   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5824   if(!step) return;
5825   lastX = x; lastY = y;
5826
5827   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5828   if(endPV < 0) return;
5829   if(y < margin) step = 1; else
5830   if(y > h - margin) step = -1;
5831   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5832   currentMove += step;
5833   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5834   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5835                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5836   DrawPosition(FALSE, boards[currentMove]);
5837 }
5838
5839
5840 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5841 // All positions will have equal probability, but the current method will not provide a unique
5842 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5843 #define DARK 1
5844 #define LITE 2
5845 #define ANY 3
5846
5847 int squaresLeft[4];
5848 int piecesLeft[(int)BlackPawn];
5849 int seed, nrOfShuffles;
5850
5851 void
5852 GetPositionNumber ()
5853 {       // sets global variable seed
5854         int i;
5855
5856         seed = appData.defaultFrcPosition;
5857         if(seed < 0) { // randomize based on time for negative FRC position numbers
5858                 for(i=0; i<50; i++) seed += random();
5859                 seed = random() ^ random() >> 8 ^ random() << 8;
5860                 if(seed<0) seed = -seed;
5861         }
5862 }
5863
5864 int
5865 put (Board board, int pieceType, int rank, int n, int shade)
5866 // put the piece on the (n-1)-th empty squares of the given shade
5867 {
5868         int i;
5869
5870         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5871                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5872                         board[rank][i] = (ChessSquare) pieceType;
5873                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5874                         squaresLeft[ANY]--;
5875                         piecesLeft[pieceType]--;
5876                         return i;
5877                 }
5878         }
5879         return -1;
5880 }
5881
5882
5883 void
5884 AddOnePiece (Board board, int pieceType, int rank, int shade)
5885 // calculate where the next piece goes, (any empty square), and put it there
5886 {
5887         int i;
5888
5889         i = seed % squaresLeft[shade];
5890         nrOfShuffles *= squaresLeft[shade];
5891         seed /= squaresLeft[shade];
5892         put(board, pieceType, rank, i, shade);
5893 }
5894
5895 void
5896 AddTwoPieces (Board board, int pieceType, int rank)
5897 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5898 {
5899         int i, n=squaresLeft[ANY], j=n-1, k;
5900
5901         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5902         i = seed % k;  // pick one
5903         nrOfShuffles *= k;
5904         seed /= k;
5905         while(i >= j) i -= j--;
5906         j = n - 1 - j; i += j;
5907         put(board, pieceType, rank, j, ANY);
5908         put(board, pieceType, rank, i, ANY);
5909 }
5910
5911 void
5912 SetUpShuffle (Board board, int number)
5913 {
5914         int i, p, first=1;
5915
5916         GetPositionNumber(); nrOfShuffles = 1;
5917
5918         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5919         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5920         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5921
5922         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5923
5924         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5925             p = (int) board[0][i];
5926             if(p < (int) BlackPawn) piecesLeft[p] ++;
5927             board[0][i] = EmptySquare;
5928         }
5929
5930         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5931             // shuffles restricted to allow normal castling put KRR first
5932             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5933                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5934             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5935                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5936             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5937                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5938             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5939                 put(board, WhiteRook, 0, 0, ANY);
5940             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5941         }
5942
5943         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5944             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5945             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5946                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5947                 while(piecesLeft[p] >= 2) {
5948                     AddOnePiece(board, p, 0, LITE);
5949                     AddOnePiece(board, p, 0, DARK);
5950                 }
5951                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5952             }
5953
5954         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5955             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5956             // but we leave King and Rooks for last, to possibly obey FRC restriction
5957             if(p == (int)WhiteRook) continue;
5958             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5959             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5960         }
5961
5962         // now everything is placed, except perhaps King (Unicorn) and Rooks
5963
5964         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5965             // Last King gets castling rights
5966             while(piecesLeft[(int)WhiteUnicorn]) {
5967                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5968                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5969             }
5970
5971             while(piecesLeft[(int)WhiteKing]) {
5972                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5973                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5974             }
5975
5976
5977         } else {
5978             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5979             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5980         }
5981
5982         // Only Rooks can be left; simply place them all
5983         while(piecesLeft[(int)WhiteRook]) {
5984                 i = put(board, WhiteRook, 0, 0, ANY);
5985                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5986                         if(first) {
5987                                 first=0;
5988                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5989                         }
5990                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5991                 }
5992         }
5993         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5994             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5995         }
5996
5997         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5998 }
5999
6000 int
6001 ptclen (const char *s, char *escapes)
6002 {
6003     int n = 0;
6004     if(!*escapes) return strlen(s);
6005     while(*s) n += (*s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)), s++;
6006     return n;
6007 }
6008
6009 static int pieceOrder[] = {
6010   0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, // P N B R Q F E A C W M
6011  11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, // O H I J G D V L S U Lion
6012  45, 23, 24, 25, 26, 27, 28, 29, 46, 31, 32, // Sword Zebra Camel Tower Wolf Dragon Duck Axe Leopard Gnu Cub
6013  44, 51, 56, 57, 58, 59, 60, 61, 62, 63, 34, // Whale Pegasus Wizard Copper Iron Viking Flag Amazon Wheel Shield Claw
6014  33, 55, 53, 42, 37, 48, 39, 40, 41, 22, 30, // +P +N =B =R +L +S +E +Ph +Kn Butterfly Hat
6015  38, 43, 35, 36, 49, 47, 52, 50, 54, 64, 65 // +V +M =H =D Princess HSword +GB HCrown Wheer Shierd King
6016 };
6017
6018 int
6019 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6020 /* [HGM] moved here from winboard.c because of its general usefulness */
6021 /*       Basically a safe strcpy that uses the last character as King */
6022 {
6023     int result = FALSE; int NrPieces;
6024     unsigned char partner[EmptySquare];
6025
6026     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6027                     && NrPieces >= 12 && !(NrPieces&1)) {
6028         int i, ii, j = 0; /* [HGM] Accept even length from 12 to 88 */
6029
6030         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6031         for( ii=0; ii<NrPieces/2-1; ii++ ) {
6032             char *p, c=0;
6033             i = pieceOrder[ii];
6034             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6035             table[i] = map[j++];
6036             if(p = strchr(escapes, map[j])) j++, table[i] += 64*(p - escapes + 1);
6037             if(c) partner[i] = table[i], table[i] = c;
6038         }
6039         table[(int) WhiteKing]  = map[j++];
6040         for( ii=0; ii<NrPieces/2-1; ii++ ) {
6041             char *p, c=0;
6042             i = WHITE_TO_BLACK pieceOrder[ii];
6043             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6044             table[i] = map[j++];
6045             if(p = strchr(escapes, map[j])) j++, table[i] += 64*(p - escapes + 1);
6046             if(c) partner[i] = table[i], table[i] = c;
6047         }
6048         table[(int) BlackKing]  = map[j++];
6049
6050
6051         if(*escapes) { // set up promotion pairing
6052             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6053             // pieceToChar entirely filled, so we can look up specified partners
6054             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6055                 int c = table[i];
6056                 if(c == '^' || c == '-') { // has specified partner
6057                     int p;
6058                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6059                     if(c == '^') table[i] = '+';
6060                     if(p < EmptySquare) promoPartner[p] = i, promoPartner[i] = p; // marry them
6061                 } else if(c == '*') promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6062             }
6063         }
6064
6065         result = TRUE;
6066     }
6067
6068     return result;
6069 }
6070
6071 int
6072 SetCharTable (unsigned char *table, const char * map)
6073 {
6074     return SetCharTableEsc(table, map, "");
6075 }
6076
6077 void
6078 Prelude (Board board)
6079 {       // [HGM] superchess: random selection of exo-pieces
6080         int i, j, k; ChessSquare p;
6081         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6082
6083         GetPositionNumber(); // use FRC position number
6084
6085         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6086             SetCharTable(pieceToChar, appData.pieceToCharTable);
6087             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6088                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6089         }
6090
6091         j = seed%4;                 seed /= 4;
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 >= j); seed /= 3;
6096         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = 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%3;                 seed /= 3;
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%2 + (seed%2 >= j); seed /= 2;
6104         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6105         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6106         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6107         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6108         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6109         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6110         put(board, exoPieces[0],    0, 0, ANY);
6111         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6112 }
6113
6114 void
6115 InitPosition (int redraw)
6116 {
6117     ChessSquare (* pieces)[BOARD_FILES];
6118     int i, j, pawnRow=1, pieceRows=1, overrule,
6119     oldx = gameInfo.boardWidth,
6120     oldy = gameInfo.boardHeight,
6121     oldh = gameInfo.holdingsWidth;
6122     static int oldv;
6123
6124     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6125
6126     /* [AS] Initialize pv info list [HGM] and game status */
6127     {
6128         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6129             pvInfoList[i].depth = 0;
6130             boards[i][EP_STATUS] = EP_NONE;
6131             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6132         }
6133
6134         initialRulePlies = 0; /* 50-move counter start */
6135
6136         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6137         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6138     }
6139
6140
6141     /* [HGM] logic here is completely changed. In stead of full positions */
6142     /* the initialized data only consist of the two backranks. The switch */
6143     /* selects which one we will use, which is than copied to the Board   */
6144     /* initialPosition, which for the rest is initialized by Pawns and    */
6145     /* empty squares. This initial position is then copied to boards[0],  */
6146     /* possibly after shuffling, so that it remains available.            */
6147
6148     gameInfo.holdingsWidth = 0; /* default board sizes */
6149     gameInfo.boardWidth    = 8;
6150     gameInfo.boardHeight   = 8;
6151     gameInfo.holdingsSize  = 0;
6152     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6153     for(i=0; i<BOARD_FILES-6; i++)
6154       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6155     initialPosition[EP_STATUS] = EP_NONE;
6156     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6157     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6158     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6159          SetCharTable(pieceNickName, appData.pieceNickNames);
6160     else SetCharTable(pieceNickName, "............");
6161     pieces = FIDEArray;
6162
6163     switch (gameInfo.variant) {
6164     case VariantFischeRandom:
6165       shuffleOpenings = TRUE;
6166       appData.fischerCastling = TRUE;
6167     default:
6168       break;
6169     case VariantShatranj:
6170       pieces = ShatranjArray;
6171       nrCastlingRights = 0;
6172       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6173       break;
6174     case VariantMakruk:
6175       pieces = makrukArray;
6176       nrCastlingRights = 0;
6177       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6178       break;
6179     case VariantASEAN:
6180       pieces = aseanArray;
6181       nrCastlingRights = 0;
6182       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6183       break;
6184     case VariantTwoKings:
6185       pieces = twoKingsArray;
6186       break;
6187     case VariantGrand:
6188       pieces = GrandArray;
6189       nrCastlingRights = 0;
6190       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6191       gameInfo.boardWidth = 10;
6192       gameInfo.boardHeight = 10;
6193       gameInfo.holdingsSize = 7;
6194       break;
6195     case VariantCapaRandom:
6196       shuffleOpenings = TRUE;
6197       appData.fischerCastling = TRUE;
6198     case VariantCapablanca:
6199       pieces = CapablancaArray;
6200       gameInfo.boardWidth = 10;
6201       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6202       break;
6203     case VariantGothic:
6204       pieces = GothicArray;
6205       gameInfo.boardWidth = 10;
6206       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6207       break;
6208     case VariantSChess:
6209       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6210       gameInfo.holdingsSize = 7;
6211       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6212       break;
6213     case VariantJanus:
6214       pieces = JanusArray;
6215       gameInfo.boardWidth = 10;
6216       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6217       nrCastlingRights = 6;
6218         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6219         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6220         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6221         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6222         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6223         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6224       break;
6225     case VariantFalcon:
6226       pieces = FalconArray;
6227       gameInfo.boardWidth = 10;
6228       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6229       break;
6230     case VariantXiangqi:
6231       pieces = XiangqiArray;
6232       gameInfo.boardWidth  = 9;
6233       gameInfo.boardHeight = 10;
6234       nrCastlingRights = 0;
6235       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6236       break;
6237     case VariantShogi:
6238       pieces = ShogiArray;
6239       gameInfo.boardWidth  = 9;
6240       gameInfo.boardHeight = 9;
6241       gameInfo.holdingsSize = 7;
6242       nrCastlingRights = 0;
6243       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6244       break;
6245     case VariantChu:
6246       pieces = ChuArray; pieceRows = 3;
6247       gameInfo.boardWidth  = 12;
6248       gameInfo.boardHeight = 12;
6249       nrCastlingRights = 0;
6250       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN/^P.^B^R.^S^E^X^O^G^C^A^T^H^D.^V^M^L^I^FK"
6251                                    "p.brqsexogcathd.vmlifn/^p.^b^r.^s^e^x^o^g^c^a^t^h^d.^v^m^l^i^fk", SUFFIXES);
6252       break;
6253     case VariantCourier:
6254       pieces = CourierArray;
6255       gameInfo.boardWidth  = 12;
6256       nrCastlingRights = 0;
6257       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6258       break;
6259     case VariantKnightmate:
6260       pieces = KnightmateArray;
6261       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6262       break;
6263     case VariantSpartan:
6264       pieces = SpartanArray;
6265       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6266       break;
6267     case VariantLion:
6268       pieces = lionArray;
6269       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6270       break;
6271     case VariantChuChess:
6272       pieces = ChuChessArray;
6273       gameInfo.boardWidth = 10;
6274       gameInfo.boardHeight = 10;
6275       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6276       break;
6277     case VariantFairy:
6278       pieces = fairyArray;
6279       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6280       break;
6281     case VariantGreat:
6282       pieces = GreatArray;
6283       gameInfo.boardWidth = 10;
6284       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6285       gameInfo.holdingsSize = 8;
6286       break;
6287     case VariantSuper:
6288       pieces = FIDEArray;
6289       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6290       gameInfo.holdingsSize = 8;
6291       startedFromSetupPosition = TRUE;
6292       break;
6293     case VariantCrazyhouse:
6294     case VariantBughouse:
6295       pieces = FIDEArray;
6296       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6297       gameInfo.holdingsSize = 5;
6298       break;
6299     case VariantWildCastle:
6300       pieces = FIDEArray;
6301       /* !!?shuffle with kings guaranteed to be on d or e file */
6302       shuffleOpenings = 1;
6303       break;
6304     case VariantNoCastle:
6305       pieces = FIDEArray;
6306       nrCastlingRights = 0;
6307       /* !!?unconstrained back-rank shuffle */
6308       shuffleOpenings = 1;
6309       break;
6310     }
6311
6312     overrule = 0;
6313     if(appData.NrFiles >= 0) {
6314         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6315         gameInfo.boardWidth = appData.NrFiles;
6316     }
6317     if(appData.NrRanks >= 0) {
6318         gameInfo.boardHeight = appData.NrRanks;
6319     }
6320     if(appData.holdingsSize >= 0) {
6321         i = appData.holdingsSize;
6322         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6323         gameInfo.holdingsSize = i;
6324     }
6325     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6326     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6327         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6328
6329     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6330     if(pawnRow < 1) pawnRow = 1;
6331     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6332        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6333     if(gameInfo.variant == VariantChu) pawnRow = 3;
6334
6335     /* User pieceToChar list overrules defaults */
6336     if(appData.pieceToCharTable != NULL)
6337         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6338
6339     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6340
6341         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6342             s = (ChessSquare) 0; /* account holding counts in guard band */
6343         for( i=0; i<BOARD_HEIGHT; i++ )
6344             initialPosition[i][j] = s;
6345
6346         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6347         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6348         initialPosition[pawnRow][j] = WhitePawn;
6349         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6350         if(gameInfo.variant == VariantXiangqi) {
6351             if(j&1) {
6352                 initialPosition[pawnRow][j] =
6353                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6354                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6355                    initialPosition[2][j] = WhiteCannon;
6356                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6357                 }
6358             }
6359         }
6360         if(gameInfo.variant == VariantChu) {
6361              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6362                initialPosition[pawnRow+1][j] = WhiteCobra,
6363                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6364              for(i=1; i<pieceRows; i++) {
6365                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6366                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6367              }
6368         }
6369         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6370             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6371                initialPosition[0][j] = WhiteRook;
6372                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6373             }
6374         }
6375         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6376     }
6377     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6378     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6379
6380             j=BOARD_LEFT+1;
6381             initialPosition[1][j] = WhiteBishop;
6382             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6383             j=BOARD_RGHT-2;
6384             initialPosition[1][j] = WhiteRook;
6385             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6386     }
6387
6388     if( nrCastlingRights == -1) {
6389         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6390         /*       This sets default castling rights from none to normal corners   */
6391         /* Variants with other castling rights must set them themselves above    */
6392         nrCastlingRights = 6;
6393
6394         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6395         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6396         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6397         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6398         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6399         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6400      }
6401
6402      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6403      if(gameInfo.variant == VariantGreat) { // promotion commoners
6404         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6405         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6406         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6407         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6408      }
6409      if( gameInfo.variant == VariantSChess ) {
6410       initialPosition[1][0] = BlackMarshall;
6411       initialPosition[2][0] = BlackAngel;
6412       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6413       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6414       initialPosition[1][1] = initialPosition[2][1] =
6415       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6416      }
6417   if (appData.debugMode) {
6418     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6419   }
6420     if(shuffleOpenings) {
6421         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6422         startedFromSetupPosition = TRUE;
6423     }
6424     if(startedFromPositionFile) {
6425       /* [HGM] loadPos: use PositionFile for every new game */
6426       CopyBoard(initialPosition, filePosition);
6427       for(i=0; i<nrCastlingRights; i++)
6428           initialRights[i] = filePosition[CASTLING][i];
6429       startedFromSetupPosition = TRUE;
6430     }
6431
6432     CopyBoard(boards[0], initialPosition);
6433
6434     if(oldx != gameInfo.boardWidth ||
6435        oldy != gameInfo.boardHeight ||
6436        oldv != gameInfo.variant ||
6437        oldh != gameInfo.holdingsWidth
6438                                          )
6439             InitDrawingSizes(-2 ,0);
6440
6441     oldv = gameInfo.variant;
6442     if (redraw)
6443       DrawPosition(TRUE, boards[currentMove]);
6444 }
6445
6446 void
6447 SendBoard (ChessProgramState *cps, int moveNum)
6448 {
6449     char message[MSG_SIZ];
6450
6451     if (cps->useSetboard) {
6452       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6453       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6454       SendToProgram(message, cps);
6455       free(fen);
6456
6457     } else {
6458       ChessSquare *bp;
6459       int i, j, left=0, right=BOARD_WIDTH;
6460       /* Kludge to set black to move, avoiding the troublesome and now
6461        * deprecated "black" command.
6462        */
6463       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6464         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6465
6466       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6467
6468       SendToProgram("edit\n", cps);
6469       SendToProgram("#\n", cps);
6470       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6471         bp = &boards[moveNum][i][left];
6472         for (j = left; j < right; j++, bp++) {
6473           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6474           if ((int) *bp < (int) BlackPawn) {
6475             if(j == BOARD_RGHT+1)
6476                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6477             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6478             if(message[0] == '+' || message[0] == '~') {
6479               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6480                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6481                         AAA + j, ONE + i - '0');
6482             }
6483             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6484                 message[1] = BOARD_RGHT   - 1 - j + '1';
6485                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6486             }
6487             SendToProgram(message, cps);
6488           }
6489         }
6490       }
6491
6492       SendToProgram("c\n", cps);
6493       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6494         bp = &boards[moveNum][i][left];
6495         for (j = left; j < right; j++, bp++) {
6496           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6497           if (((int) *bp != (int) EmptySquare)
6498               && ((int) *bp >= (int) BlackPawn)) {
6499             if(j == BOARD_LEFT-2)
6500                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6501             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6502                     AAA + j, ONE + i - '0');
6503             if(message[0] == '+' || message[0] == '~') {
6504               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6505                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6506                         AAA + j, ONE + i - '0');
6507             }
6508             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6509                 message[1] = BOARD_RGHT   - 1 - j + '1';
6510                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6511             }
6512             SendToProgram(message, cps);
6513           }
6514         }
6515       }
6516
6517       SendToProgram(".\n", cps);
6518     }
6519     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6520 }
6521
6522 char exclusionHeader[MSG_SIZ];
6523 int exCnt, excludePtr;
6524 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6525 static Exclusion excluTab[200];
6526 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6527
6528 static void
6529 WriteMap (int s)
6530 {
6531     int j;
6532     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6533     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6534 }
6535
6536 static void
6537 ClearMap ()
6538 {
6539     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6540     excludePtr = 24; exCnt = 0;
6541     WriteMap(0);
6542 }
6543
6544 static void
6545 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6546 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6547     char buf[2*MOVE_LEN], *p;
6548     Exclusion *e = excluTab;
6549     int i;
6550     for(i=0; i<exCnt; i++)
6551         if(e[i].ff == fromX && e[i].fr == fromY &&
6552            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6553     if(i == exCnt) { // was not in exclude list; add it
6554         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6555         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6556             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6557             return; // abort
6558         }
6559         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6560         excludePtr++; e[i].mark = excludePtr++;
6561         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6562         exCnt++;
6563     }
6564     exclusionHeader[e[i].mark] = state;
6565 }
6566
6567 static int
6568 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6569 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6570     char buf[MSG_SIZ];
6571     int j, k;
6572     ChessMove moveType;
6573     if((signed char)promoChar == -1) { // kludge to indicate best move
6574         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6575             return 1; // if unparsable, abort
6576     }
6577     // update exclusion map (resolving toggle by consulting existing state)
6578     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6579     j = k%8; k >>= 3;
6580     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6581     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6582          excludeMap[k] |=   1<<j;
6583     else excludeMap[k] &= ~(1<<j);
6584     // update header
6585     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6586     // inform engine
6587     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6588     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6589     SendToBoth(buf);
6590     return (state == '+');
6591 }
6592
6593 static void
6594 ExcludeClick (int index)
6595 {
6596     int i, j;
6597     Exclusion *e = excluTab;
6598     if(index < 25) { // none, best or tail clicked
6599         if(index < 13) { // none: include all
6600             WriteMap(0); // clear map
6601             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6602             SendToBoth("include all\n"); // and inform engine
6603         } else if(index > 18) { // tail
6604             if(exclusionHeader[19] == '-') { // tail was excluded
6605                 SendToBoth("include all\n");
6606                 WriteMap(0); // clear map completely
6607                 // now re-exclude selected moves
6608                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6609                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6610             } else { // tail was included or in mixed state
6611                 SendToBoth("exclude all\n");
6612                 WriteMap(0xFF); // fill map completely
6613                 // now re-include selected moves
6614                 j = 0; // count them
6615                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6616                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6617                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6618             }
6619         } else { // best
6620             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6621         }
6622     } else {
6623         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6624             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6625             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6626             break;
6627         }
6628     }
6629 }
6630
6631 ChessSquare
6632 DefaultPromoChoice (int white)
6633 {
6634     ChessSquare result;
6635     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6636        gameInfo.variant == VariantMakruk)
6637         result = WhiteFerz; // no choice
6638     else if(gameInfo.variant == VariantASEAN)
6639         result = WhiteRook; // no choice
6640     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6641         result= WhiteKing; // in Suicide Q is the last thing we want
6642     else if(gameInfo.variant == VariantSpartan)
6643         result = white ? WhiteQueen : WhiteAngel;
6644     else result = WhiteQueen;
6645     if(!white) result = WHITE_TO_BLACK result;
6646     return result;
6647 }
6648
6649 static int autoQueen; // [HGM] oneclick
6650
6651 int
6652 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6653 {
6654     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6655     /* [HGM] add Shogi promotions */
6656     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6657     ChessSquare piece, partner;
6658     ChessMove moveType;
6659     Boolean premove;
6660
6661     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6662     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6663
6664     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6665       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6666         return FALSE;
6667
6668     piece = boards[currentMove][fromY][fromX];
6669     if(gameInfo.variant == VariantChu) {
6670         promotionZoneSize = BOARD_HEIGHT/3;
6671         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6672     } else if(gameInfo.variant == VariantShogi) {
6673         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6674         highestPromotingPiece = (int)WhiteAlfil;
6675     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6676         promotionZoneSize = 3;
6677     }
6678
6679     // Treat Lance as Pawn when it is not representing Amazon or Lance
6680     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6681         if(piece == WhiteLance) piece = WhitePawn; else
6682         if(piece == BlackLance) piece = BlackPawn;
6683     }
6684
6685     // next weed out all moves that do not touch the promotion zone at all
6686     if((int)piece >= BlackPawn) {
6687         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6688              return FALSE;
6689         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6690         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6691     } else {
6692         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6693            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6694         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6695              return FALSE;
6696     }
6697
6698     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6699
6700     // weed out mandatory Shogi promotions
6701     if(gameInfo.variant == VariantShogi) {
6702         if(piece >= BlackPawn) {
6703             if(toY == 0 && piece == BlackPawn ||
6704                toY == 0 && piece == BlackQueen ||
6705                toY <= 1 && piece == BlackKnight) {
6706                 *promoChoice = '+';
6707                 return FALSE;
6708             }
6709         } else {
6710             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6711                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6712                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6713                 *promoChoice = '+';
6714                 return FALSE;
6715             }
6716         }
6717     }
6718
6719     // weed out obviously illegal Pawn moves
6720     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6721         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6722         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6723         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6724         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6725         // note we are not allowed to test for valid (non-)capture, due to premove
6726     }
6727
6728     // we either have a choice what to promote to, or (in Shogi) whether to promote
6729     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6730        gameInfo.variant == VariantMakruk) {
6731         ChessSquare p=BlackFerz;  // no choice
6732         while(p < EmptySquare) {  //but make sure we use piece that exists
6733             *promoChoice = PieceToChar(p++);
6734             if(*promoChoice != '.') break;
6735         }
6736         return FALSE;
6737     }
6738     // no sense asking what we must promote to if it is going to explode...
6739     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6740         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6741         return FALSE;
6742     }
6743     // give caller the default choice even if we will not make it
6744     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6745     partner = piece; // pieces can promote if the pieceToCharTable says so
6746     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6747     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6748     if(        sweepSelect && gameInfo.variant != VariantGreat
6749                            && gameInfo.variant != VariantGrand
6750                            && gameInfo.variant != VariantSuper) return FALSE;
6751     if(autoQueen) return FALSE; // predetermined
6752
6753     // suppress promotion popup on illegal moves that are not premoves
6754     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6755               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6756     if(appData.testLegality && !premove) {
6757         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6758                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6759         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6760         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6761             return FALSE;
6762     }
6763
6764     return TRUE;
6765 }
6766
6767 int
6768 InPalace (int row, int column)
6769 {   /* [HGM] for Xiangqi */
6770     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6771          column < (BOARD_WIDTH + 4)/2 &&
6772          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6773     return FALSE;
6774 }
6775
6776 int
6777 PieceForSquare (int x, int y)
6778 {
6779   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6780      return -1;
6781   else
6782      return boards[currentMove][y][x];
6783 }
6784
6785 int
6786 OKToStartUserMove (int x, int y)
6787 {
6788     ChessSquare from_piece;
6789     int white_piece;
6790
6791     if (matchMode) return FALSE;
6792     if (gameMode == EditPosition) return TRUE;
6793
6794     if (x >= 0 && y >= 0)
6795       from_piece = boards[currentMove][y][x];
6796     else
6797       from_piece = EmptySquare;
6798
6799     if (from_piece == EmptySquare) return FALSE;
6800
6801     white_piece = (int)from_piece >= (int)WhitePawn &&
6802       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6803
6804     switch (gameMode) {
6805       case AnalyzeFile:
6806       case TwoMachinesPlay:
6807       case EndOfGame:
6808         return FALSE;
6809
6810       case IcsObserving:
6811       case IcsIdle:
6812         return FALSE;
6813
6814       case MachinePlaysWhite:
6815       case IcsPlayingBlack:
6816         if (appData.zippyPlay) return FALSE;
6817         if (white_piece) {
6818             DisplayMoveError(_("You are playing Black"));
6819             return FALSE;
6820         }
6821         break;
6822
6823       case MachinePlaysBlack:
6824       case IcsPlayingWhite:
6825         if (appData.zippyPlay) return FALSE;
6826         if (!white_piece) {
6827             DisplayMoveError(_("You are playing White"));
6828             return FALSE;
6829         }
6830         break;
6831
6832       case PlayFromGameFile:
6833             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6834       case EditGame:
6835         if (!white_piece && WhiteOnMove(currentMove)) {
6836             DisplayMoveError(_("It is White's turn"));
6837             return FALSE;
6838         }
6839         if (white_piece && !WhiteOnMove(currentMove)) {
6840             DisplayMoveError(_("It is Black's turn"));
6841             return FALSE;
6842         }
6843         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6844             /* Editing correspondence game history */
6845             /* Could disallow this or prompt for confirmation */
6846             cmailOldMove = -1;
6847         }
6848         break;
6849
6850       case BeginningOfGame:
6851         if (appData.icsActive) return FALSE;
6852         if (!appData.noChessProgram) {
6853             if (!white_piece) {
6854                 DisplayMoveError(_("You are playing White"));
6855                 return FALSE;
6856             }
6857         }
6858         break;
6859
6860       case Training:
6861         if (!white_piece && WhiteOnMove(currentMove)) {
6862             DisplayMoveError(_("It is White's turn"));
6863             return FALSE;
6864         }
6865         if (white_piece && !WhiteOnMove(currentMove)) {
6866             DisplayMoveError(_("It is Black's turn"));
6867             return FALSE;
6868         }
6869         break;
6870
6871       default:
6872       case IcsExamining:
6873         break;
6874     }
6875     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6876         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6877         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6878         && gameMode != AnalyzeFile && gameMode != Training) {
6879         DisplayMoveError(_("Displayed position is not current"));
6880         return FALSE;
6881     }
6882     return TRUE;
6883 }
6884
6885 Boolean
6886 OnlyMove (int *x, int *y, Boolean captures)
6887 {
6888     DisambiguateClosure cl;
6889     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6890     switch(gameMode) {
6891       case MachinePlaysBlack:
6892       case IcsPlayingWhite:
6893       case BeginningOfGame:
6894         if(!WhiteOnMove(currentMove)) return FALSE;
6895         break;
6896       case MachinePlaysWhite:
6897       case IcsPlayingBlack:
6898         if(WhiteOnMove(currentMove)) return FALSE;
6899         break;
6900       case EditGame:
6901         break;
6902       default:
6903         return FALSE;
6904     }
6905     cl.pieceIn = EmptySquare;
6906     cl.rfIn = *y;
6907     cl.ffIn = *x;
6908     cl.rtIn = -1;
6909     cl.ftIn = -1;
6910     cl.promoCharIn = NULLCHAR;
6911     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6912     if( cl.kind == NormalMove ||
6913         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6914         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6915         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6916       fromX = cl.ff;
6917       fromY = cl.rf;
6918       *x = cl.ft;
6919       *y = cl.rt;
6920       return TRUE;
6921     }
6922     if(cl.kind != ImpossibleMove) return FALSE;
6923     cl.pieceIn = EmptySquare;
6924     cl.rfIn = -1;
6925     cl.ffIn = -1;
6926     cl.rtIn = *y;
6927     cl.ftIn = *x;
6928     cl.promoCharIn = NULLCHAR;
6929     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6930     if( cl.kind == NormalMove ||
6931         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6932         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6933         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6934       fromX = cl.ff;
6935       fromY = cl.rf;
6936       *x = cl.ft;
6937       *y = cl.rt;
6938       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6939       return TRUE;
6940     }
6941     return FALSE;
6942 }
6943
6944 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6945 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6946 int lastLoadGameUseList = FALSE;
6947 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6948 ChessMove lastLoadGameStart = EndOfFile;
6949 int doubleClick;
6950 Boolean addToBookFlag;
6951
6952 void
6953 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6954 {
6955     ChessMove moveType;
6956     ChessSquare pup;
6957     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6958
6959     /* Check if the user is playing in turn.  This is complicated because we
6960        let the user "pick up" a piece before it is his turn.  So the piece he
6961        tried to pick up may have been captured by the time he puts it down!
6962        Therefore we use the color the user is supposed to be playing in this
6963        test, not the color of the piece that is currently on the starting
6964        square---except in EditGame mode, where the user is playing both
6965        sides; fortunately there the capture race can't happen.  (It can
6966        now happen in IcsExamining mode, but that's just too bad.  The user
6967        will get a somewhat confusing message in that case.)
6968        */
6969
6970     switch (gameMode) {
6971       case AnalyzeFile:
6972       case TwoMachinesPlay:
6973       case EndOfGame:
6974       case IcsObserving:
6975       case IcsIdle:
6976         /* We switched into a game mode where moves are not accepted,
6977            perhaps while the mouse button was down. */
6978         return;
6979
6980       case MachinePlaysWhite:
6981         /* User is moving for Black */
6982         if (WhiteOnMove(currentMove)) {
6983             DisplayMoveError(_("It is White's turn"));
6984             return;
6985         }
6986         break;
6987
6988       case MachinePlaysBlack:
6989         /* User is moving for White */
6990         if (!WhiteOnMove(currentMove)) {
6991             DisplayMoveError(_("It is Black's turn"));
6992             return;
6993         }
6994         break;
6995
6996       case PlayFromGameFile:
6997             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6998       case EditGame:
6999       case IcsExamining:
7000       case BeginningOfGame:
7001       case AnalyzeMode:
7002       case Training:
7003         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7004         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7005             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7006             /* User is moving for Black */
7007             if (WhiteOnMove(currentMove)) {
7008                 DisplayMoveError(_("It is White's turn"));
7009                 return;
7010             }
7011         } else {
7012             /* User is moving for White */
7013             if (!WhiteOnMove(currentMove)) {
7014                 DisplayMoveError(_("It is Black's turn"));
7015                 return;
7016             }
7017         }
7018         break;
7019
7020       case IcsPlayingBlack:
7021         /* User is moving for Black */
7022         if (WhiteOnMove(currentMove)) {
7023             if (!appData.premove) {
7024                 DisplayMoveError(_("It is White's turn"));
7025             } else if (toX >= 0 && toY >= 0) {
7026                 premoveToX = toX;
7027                 premoveToY = toY;
7028                 premoveFromX = fromX;
7029                 premoveFromY = fromY;
7030                 premovePromoChar = promoChar;
7031                 gotPremove = 1;
7032                 if (appData.debugMode)
7033                     fprintf(debugFP, "Got premove: fromX %d,"
7034                             "fromY %d, toX %d, toY %d\n",
7035                             fromX, fromY, toX, toY);
7036             }
7037             return;
7038         }
7039         break;
7040
7041       case IcsPlayingWhite:
7042         /* User is moving for White */
7043         if (!WhiteOnMove(currentMove)) {
7044             if (!appData.premove) {
7045                 DisplayMoveError(_("It is Black's turn"));
7046             } else if (toX >= 0 && toY >= 0) {
7047                 premoveToX = toX;
7048                 premoveToY = toY;
7049                 premoveFromX = fromX;
7050                 premoveFromY = fromY;
7051                 premovePromoChar = promoChar;
7052                 gotPremove = 1;
7053                 if (appData.debugMode)
7054                     fprintf(debugFP, "Got premove: fromX %d,"
7055                             "fromY %d, toX %d, toY %d\n",
7056                             fromX, fromY, toX, toY);
7057             }
7058             return;
7059         }
7060         break;
7061
7062       default:
7063         break;
7064
7065       case EditPosition:
7066         /* EditPosition, empty square, or different color piece;
7067            click-click move is possible */
7068         if (toX == -2 || toY == -2) {
7069             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7070             DrawPosition(FALSE, boards[currentMove]);
7071             return;
7072         } else if (toX >= 0 && toY >= 0) {
7073             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7074                 ChessSquare q, p = boards[0][rf][ff];
7075                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
7076                 if(CHUPROMOTED(p) < BlackPawn) p = q = CHUPROMOTED(boards[0][rf][ff]);
7077                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
7078                 if(PieceToChar(q) == '+') gatingPiece = p;
7079             }
7080             boards[0][toY][toX] = boards[0][fromY][fromX];
7081             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7082                 if(boards[0][fromY][0] != EmptySquare) {
7083                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7084                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7085                 }
7086             } else
7087             if(fromX == BOARD_RGHT+1) {
7088                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7089                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7090                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7091                 }
7092             } else
7093             boards[0][fromY][fromX] = gatingPiece;
7094             DrawPosition(FALSE, boards[currentMove]);
7095             return;
7096         }
7097         return;
7098     }
7099
7100     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7101     pup = boards[currentMove][toY][toX];
7102
7103     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7104     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7105          if( pup != EmptySquare ) return;
7106          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7107            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7108                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7109            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7110            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7111            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7112            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7113          fromY = DROP_RANK;
7114     }
7115
7116     /* [HGM] always test for legality, to get promotion info */
7117     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7118                                          fromY, fromX, toY, toX, promoChar);
7119
7120     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7121
7122     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7123
7124     /* [HGM] but possibly ignore an IllegalMove result */
7125     if (appData.testLegality) {
7126         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7127             DisplayMoveError(_("Illegal move"));
7128             return;
7129         }
7130     }
7131
7132     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7133         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7134              ClearPremoveHighlights(); // was included
7135         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7136         return;
7137     }
7138
7139     if(addToBookFlag) { // adding moves to book
7140         char buf[MSG_SIZ], move[MSG_SIZ];
7141         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7142         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');
7143         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7144         AddBookMove(buf);
7145         addToBookFlag = FALSE;
7146         ClearHighlights();
7147         return;
7148     }
7149
7150     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7151 }
7152
7153 /* Common tail of UserMoveEvent and DropMenuEvent */
7154 int
7155 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7156 {
7157     char *bookHit = 0;
7158
7159     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7160         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7161         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7162         if(WhiteOnMove(currentMove)) {
7163             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7164         } else {
7165             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7166         }
7167     }
7168
7169     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7170        move type in caller when we know the move is a legal promotion */
7171     if(moveType == NormalMove && promoChar)
7172         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7173
7174     /* [HGM] <popupFix> The following if has been moved here from
7175        UserMoveEvent(). Because it seemed to belong here (why not allow
7176        piece drops in training games?), and because it can only be
7177        performed after it is known to what we promote. */
7178     if (gameMode == Training) {
7179       /* compare the move played on the board to the next move in the
7180        * game. If they match, display the move and the opponent's response.
7181        * If they don't match, display an error message.
7182        */
7183       int saveAnimate;
7184       Board testBoard;
7185       CopyBoard(testBoard, boards[currentMove]);
7186       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7187
7188       if (CompareBoards(testBoard, boards[currentMove+1])) {
7189         ForwardInner(currentMove+1);
7190
7191         /* Autoplay the opponent's response.
7192          * if appData.animate was TRUE when Training mode was entered,
7193          * the response will be animated.
7194          */
7195         saveAnimate = appData.animate;
7196         appData.animate = animateTraining;
7197         ForwardInner(currentMove+1);
7198         appData.animate = saveAnimate;
7199
7200         /* check for the end of the game */
7201         if (currentMove >= forwardMostMove) {
7202           gameMode = PlayFromGameFile;
7203           ModeHighlight();
7204           SetTrainingModeOff();
7205           DisplayInformation(_("End of game"));
7206         }
7207       } else {
7208         DisplayError(_("Incorrect move"), 0);
7209       }
7210       return 1;
7211     }
7212
7213   /* Ok, now we know that the move is good, so we can kill
7214      the previous line in Analysis Mode */
7215   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7216                                 && currentMove < forwardMostMove) {
7217     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7218     else forwardMostMove = currentMove;
7219   }
7220
7221   ClearMap();
7222
7223   /* If we need the chess program but it's dead, restart it */
7224   ResurrectChessProgram();
7225
7226   /* A user move restarts a paused game*/
7227   if (pausing)
7228     PauseEvent();
7229
7230   thinkOutput[0] = NULLCHAR;
7231
7232   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7233
7234   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7235     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7236     return 1;
7237   }
7238
7239   if (gameMode == BeginningOfGame) {
7240     if (appData.noChessProgram) {
7241       gameMode = EditGame;
7242       SetGameInfo();
7243     } else {
7244       char buf[MSG_SIZ];
7245       gameMode = MachinePlaysBlack;
7246       StartClocks();
7247       SetGameInfo();
7248       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7249       DisplayTitle(buf);
7250       if (first.sendName) {
7251         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7252         SendToProgram(buf, &first);
7253       }
7254       StartClocks();
7255     }
7256     ModeHighlight();
7257   }
7258
7259   /* Relay move to ICS or chess engine */
7260   if (appData.icsActive) {
7261     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7262         gameMode == IcsExamining) {
7263       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7264         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7265         SendToICS("draw ");
7266         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7267       }
7268       // also send plain move, in case ICS does not understand atomic claims
7269       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7270       ics_user_moved = 1;
7271     }
7272   } else {
7273     if (first.sendTime && (gameMode == BeginningOfGame ||
7274                            gameMode == MachinePlaysWhite ||
7275                            gameMode == MachinePlaysBlack)) {
7276       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7277     }
7278     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7279          // [HGM] book: if program might be playing, let it use book
7280         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7281         first.maybeThinking = TRUE;
7282     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7283         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7284         SendBoard(&first, currentMove+1);
7285         if(second.analyzing) {
7286             if(!second.useSetboard) SendToProgram("undo\n", &second);
7287             SendBoard(&second, currentMove+1);
7288         }
7289     } else {
7290         SendMoveToProgram(forwardMostMove-1, &first);
7291         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7292     }
7293     if (currentMove == cmailOldMove + 1) {
7294       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7295     }
7296   }
7297
7298   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7299
7300   switch (gameMode) {
7301   case EditGame:
7302     if(appData.testLegality)
7303     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7304     case MT_NONE:
7305     case MT_CHECK:
7306       break;
7307     case MT_CHECKMATE:
7308     case MT_STAINMATE:
7309       if (WhiteOnMove(currentMove)) {
7310         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7311       } else {
7312         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7313       }
7314       break;
7315     case MT_STALEMATE:
7316       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7317       break;
7318     }
7319     break;
7320
7321   case MachinePlaysBlack:
7322   case MachinePlaysWhite:
7323     /* disable certain menu options while machine is thinking */
7324     SetMachineThinkingEnables();
7325     break;
7326
7327   default:
7328     break;
7329   }
7330
7331   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7332   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7333
7334   if(bookHit) { // [HGM] book: simulate book reply
7335         static char bookMove[MSG_SIZ]; // a bit generous?
7336
7337         programStats.nodes = programStats.depth = programStats.time =
7338         programStats.score = programStats.got_only_move = 0;
7339         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7340
7341         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7342         strcat(bookMove, bookHit);
7343         HandleMachineMove(bookMove, &first);
7344   }
7345   return 1;
7346 }
7347
7348 void
7349 MarkByFEN(char *fen)
7350 {
7351         int r, f;
7352         if(!appData.markers || !appData.highlightDragging) return;
7353         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7354         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7355         while(*fen) {
7356             int s = 0;
7357             marker[r][f] = 0;
7358             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7359             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7360             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7361             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7362             if(*fen == 'T') marker[r][f++] = 0; else
7363             if(*fen == 'Y') marker[r][f++] = 1; else
7364             if(*fen == 'G') marker[r][f++] = 3; else
7365             if(*fen == 'B') marker[r][f++] = 4; else
7366             if(*fen == 'C') marker[r][f++] = 5; else
7367             if(*fen == 'M') marker[r][f++] = 6; else
7368             if(*fen == 'W') marker[r][f++] = 7; else
7369             if(*fen == 'D') marker[r][f++] = 8; else
7370             if(*fen == 'R') marker[r][f++] = 2; else {
7371                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7372               f += s; fen -= s>0;
7373             }
7374             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7375             if(r < 0) break;
7376             fen++;
7377         }
7378         DrawPosition(TRUE, NULL);
7379 }
7380
7381 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7382
7383 void
7384 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7385 {
7386     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7387     Markers *m = (Markers *) closure;
7388     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7389         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7390                          || kind == WhiteCapturesEnPassant
7391                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 3;
7392     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7393 }
7394
7395 static int hoverSavedValid;
7396
7397 void
7398 MarkTargetSquares (int clear)
7399 {
7400   int x, y, sum=0;
7401   if(clear) { // no reason to ever suppress clearing
7402     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7403     hoverSavedValid = 0;
7404     if(!sum) return; // nothing was cleared,no redraw needed
7405   } else {
7406     int capt = 0;
7407     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7408        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7409     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7410     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7411       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7412       if(capt)
7413       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7414     }
7415   }
7416   DrawPosition(FALSE, NULL);
7417 }
7418
7419 int
7420 Explode (Board board, int fromX, int fromY, int toX, int toY)
7421 {
7422     if(gameInfo.variant == VariantAtomic &&
7423        (board[toY][toX] != EmptySquare ||                     // capture?
7424         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7425                          board[fromY][fromX] == BlackPawn   )
7426       )) {
7427         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7428         return TRUE;
7429     }
7430     return FALSE;
7431 }
7432
7433 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7434
7435 int
7436 CanPromote (ChessSquare piece, int y)
7437 {
7438         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7439         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7440         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7441         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7442            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7443            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7444          gameInfo.variant == VariantMakruk) return FALSE;
7445         return (piece == BlackPawn && y <= zone ||
7446                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7447                 piece == BlackLance && y <= zone ||
7448                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7449 }
7450
7451 void
7452 HoverEvent (int xPix, int yPix, int x, int y)
7453 {
7454         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7455         int r, f;
7456         if(!first.highlight) return;
7457         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7458         if(x == oldX && y == oldY) return; // only do something if we enter new square
7459         oldFromX = fromX; oldFromY = fromY;
7460         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7461           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7462             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7463           hoverSavedValid = 1;
7464         } else if(oldX != x || oldY != y) {
7465           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7466           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7467           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7468             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7469           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7470             char buf[MSG_SIZ];
7471             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7472             SendToProgram(buf, &first);
7473           }
7474           oldX = x; oldY = y;
7475 //        SetHighlights(fromX, fromY, x, y);
7476         }
7477 }
7478
7479 void ReportClick(char *action, int x, int y)
7480 {
7481         char buf[MSG_SIZ]; // Inform engine of what user does
7482         int r, f;
7483         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7484           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7485             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7486         if(!first.highlight || gameMode == EditPosition) return;
7487         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7488         SendToProgram(buf, &first);
7489 }
7490
7491 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7492
7493 void
7494 LeftClick (ClickType clickType, int xPix, int yPix)
7495 {
7496     int x, y;
7497     Boolean saveAnimate;
7498     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7499     char promoChoice = NULLCHAR;
7500     ChessSquare piece;
7501     static TimeMark lastClickTime, prevClickTime;
7502
7503     x = EventToSquare(xPix, BOARD_WIDTH);
7504     y = EventToSquare(yPix, BOARD_HEIGHT);
7505     if (!flipView && y >= 0) {
7506         y = BOARD_HEIGHT - 1 - y;
7507     }
7508     if (flipView && x >= 0) {
7509         x = BOARD_WIDTH - 1 - x;
7510     }
7511
7512     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7513         static int dummy;
7514         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7515         right = TRUE;
7516         return;
7517     }
7518
7519     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7520
7521     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7522
7523     if (clickType == Press) ErrorPopDown();
7524     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7525
7526     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7527         defaultPromoChoice = promoSweep;
7528         promoSweep = EmptySquare;   // terminate sweep
7529         promoDefaultAltered = TRUE;
7530         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7531     }
7532
7533     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7534         if(clickType == Release) return; // ignore upclick of click-click destination
7535         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7536         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7537         if(gameInfo.holdingsWidth &&
7538                 (WhiteOnMove(currentMove)
7539                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7540                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7541             // click in right holdings, for determining promotion piece
7542             ChessSquare p = boards[currentMove][y][x];
7543             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7544             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7545             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7546                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7547                 fromX = fromY = -1;
7548                 return;
7549             }
7550         }
7551         DrawPosition(FALSE, boards[currentMove]);
7552         return;
7553     }
7554
7555     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7556     if(clickType == Press
7557             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7558               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7559               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7560         return;
7561
7562     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7563         // could be static click on premove from-square: abort premove
7564         gotPremove = 0;
7565         ClearPremoveHighlights();
7566     }
7567
7568     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7569         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7570
7571     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7572         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7573                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7574         defaultPromoChoice = DefaultPromoChoice(side);
7575     }
7576
7577     autoQueen = appData.alwaysPromoteToQueen;
7578
7579     if (fromX == -1) {
7580       int originalY = y;
7581       gatingPiece = EmptySquare;
7582       if (clickType != Press) {
7583         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7584             DragPieceEnd(xPix, yPix); dragging = 0;
7585             DrawPosition(FALSE, NULL);
7586         }
7587         return;
7588       }
7589       doubleClick = FALSE;
7590       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7591         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7592       }
7593       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7594       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7595          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7596          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7597             /* First square */
7598             if (OKToStartUserMove(fromX, fromY)) {
7599                 second = 0;
7600                 ReportClick("lift", x, y);
7601                 MarkTargetSquares(0);
7602                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7603                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7604                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7605                     promoSweep = defaultPromoChoice;
7606                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7607                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7608                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7609                 }
7610                 if (appData.highlightDragging) {
7611                     SetHighlights(fromX, fromY, -1, -1);
7612                 } else {
7613                     ClearHighlights();
7614                 }
7615             } else fromX = fromY = -1;
7616             return;
7617         }
7618     }
7619
7620     /* fromX != -1 */
7621     if (clickType == Press && gameMode != EditPosition) {
7622         ChessSquare fromP;
7623         ChessSquare toP;
7624         int frc;
7625
7626         // ignore off-board to clicks
7627         if(y < 0 || x < 0) return;
7628
7629         /* Check if clicking again on the same color piece */
7630         fromP = boards[currentMove][fromY][fromX];
7631         toP = boards[currentMove][y][x];
7632         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7633         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7634             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7635            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7636              WhitePawn <= toP && toP <= WhiteKing &&
7637              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7638              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7639             (BlackPawn <= fromP && fromP <= BlackKing &&
7640              BlackPawn <= toP && toP <= BlackKing &&
7641              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7642              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7643             /* Clicked again on same color piece -- changed his mind */
7644             second = (x == fromX && y == fromY);
7645             killX = killY = -1;
7646             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7647                 second = FALSE; // first double-click rather than scond click
7648                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7649             }
7650             promoDefaultAltered = FALSE;
7651             MarkTargetSquares(1);
7652            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7653             if (appData.highlightDragging) {
7654                 SetHighlights(x, y, -1, -1);
7655             } else {
7656                 ClearHighlights();
7657             }
7658             if (OKToStartUserMove(x, y)) {
7659                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7660                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7661                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7662                  gatingPiece = boards[currentMove][fromY][fromX];
7663                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7664                 fromX = x;
7665                 fromY = y; dragging = 1;
7666                 if(!second) ReportClick("lift", x, y);
7667                 MarkTargetSquares(0);
7668                 DragPieceBegin(xPix, yPix, FALSE);
7669                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7670                     promoSweep = defaultPromoChoice;
7671                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7672                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7673                 }
7674             }
7675            }
7676            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7677            second = FALSE;
7678         }
7679         // ignore clicks on holdings
7680         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7681     }
7682
7683     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7684         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7685         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7686         return;
7687     }
7688
7689     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7690         DragPieceEnd(xPix, yPix); dragging = 0;
7691         if(clearFlag) {
7692             // a deferred attempt to click-click move an empty square on top of a piece
7693             boards[currentMove][y][x] = EmptySquare;
7694             ClearHighlights();
7695             DrawPosition(FALSE, boards[currentMove]);
7696             fromX = fromY = -1; clearFlag = 0;
7697             return;
7698         }
7699         if (appData.animateDragging) {
7700             /* Undo animation damage if any */
7701             DrawPosition(FALSE, NULL);
7702         }
7703         if (second) {
7704             /* Second up/down in same square; just abort move */
7705             second = 0;
7706             fromX = fromY = -1;
7707             gatingPiece = EmptySquare;
7708             MarkTargetSquares(1);
7709             ClearHighlights();
7710             gotPremove = 0;
7711             ClearPremoveHighlights();
7712         } else {
7713             /* First upclick in same square; start click-click mode */
7714             SetHighlights(x, y, -1, -1);
7715         }
7716         return;
7717     }
7718
7719     clearFlag = 0;
7720
7721     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7722        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7723         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7724         DisplayMessage(_("only marked squares are legal"),"");
7725         DrawPosition(TRUE, NULL);
7726         return; // ignore to-click
7727     }
7728
7729     /* we now have a different from- and (possibly off-board) to-square */
7730     /* Completed move */
7731     if(!sweepSelecting) {
7732         toX = x;
7733         toY = y;
7734     }
7735
7736     piece = boards[currentMove][fromY][fromX];
7737
7738     saveAnimate = appData.animate;
7739     if (clickType == Press) {
7740         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7741         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7742             // must be Edit Position mode with empty-square selected
7743             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7744             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7745             return;
7746         }
7747         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7748             return;
7749         }
7750         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7751             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7752         } else
7753         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7754         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7755           if(appData.sweepSelect) {
7756             promoSweep = defaultPromoChoice;
7757             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7758             selectFlag = 0; lastX = xPix; lastY = yPix;
7759             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7760             Sweep(0); // Pawn that is going to promote: preview promotion piece
7761             sweepSelecting = 1;
7762             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7763             MarkTargetSquares(1);
7764           }
7765           return; // promo popup appears on up-click
7766         }
7767         /* Finish clickclick move */
7768         if (appData.animate || appData.highlightLastMove) {
7769             SetHighlights(fromX, fromY, toX, toY);
7770         } else {
7771             ClearHighlights();
7772         }
7773     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7774         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7775         *promoRestrict = 0;
7776         if (appData.animate || appData.highlightLastMove) {
7777             SetHighlights(fromX, fromY, toX, toY);
7778         } else {
7779             ClearHighlights();
7780         }
7781     } else {
7782 #if 0
7783 // [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
7784         /* Finish drag move */
7785         if (appData.highlightLastMove) {
7786             SetHighlights(fromX, fromY, toX, toY);
7787         } else {
7788             ClearHighlights();
7789         }
7790 #endif
7791         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7792         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7793           dragging *= 2;            // flag button-less dragging if we are dragging
7794           MarkTargetSquares(1);
7795           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7796           else {
7797             kill2X = killX; kill2Y = killY;
7798             killX = x; killY = y;     //remeber this square as intermediate
7799             ReportClick("put", x, y); // and inform engine
7800             ReportClick("lift", x, y);
7801             MarkTargetSquares(0);
7802             return;
7803           }
7804         }
7805         DragPieceEnd(xPix, yPix); dragging = 0;
7806         /* Don't animate move and drag both */
7807         appData.animate = FALSE;
7808     }
7809
7810     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7811     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7812         ChessSquare piece = boards[currentMove][fromY][fromX];
7813         if(gameMode == EditPosition && piece != EmptySquare &&
7814            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7815             int n;
7816
7817             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7818                 n = PieceToNumber(piece - (int)BlackPawn);
7819                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7820                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7821                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7822             } else
7823             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7824                 n = PieceToNumber(piece);
7825                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7826                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7827                 boards[currentMove][n][BOARD_WIDTH-2]++;
7828             }
7829             boards[currentMove][fromY][fromX] = EmptySquare;
7830         }
7831         ClearHighlights();
7832         fromX = fromY = -1;
7833         MarkTargetSquares(1);
7834         DrawPosition(TRUE, boards[currentMove]);
7835         return;
7836     }
7837
7838     // off-board moves should not be highlighted
7839     if(x < 0 || y < 0) ClearHighlights();
7840     else ReportClick("put", x, y);
7841
7842     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7843
7844     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7845
7846     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7847         SetHighlights(fromX, fromY, toX, toY);
7848         MarkTargetSquares(1);
7849         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7850             // [HGM] super: promotion to captured piece selected from holdings
7851             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7852             promotionChoice = TRUE;
7853             // kludge follows to temporarily execute move on display, without promoting yet
7854             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7855             boards[currentMove][toY][toX] = p;
7856             DrawPosition(FALSE, boards[currentMove]);
7857             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7858             boards[currentMove][toY][toX] = q;
7859             DisplayMessage("Click in holdings to choose piece", "");
7860             return;
7861         }
7862         PromotionPopUp(promoChoice);
7863     } else {
7864         int oldMove = currentMove;
7865         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7866         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7867         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7868         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7869            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7870             DrawPosition(TRUE, boards[currentMove]);
7871         MarkTargetSquares(1);
7872         fromX = fromY = -1;
7873     }
7874     appData.animate = saveAnimate;
7875     if (appData.animate || appData.animateDragging) {
7876         /* Undo animation damage if needed */
7877         DrawPosition(FALSE, NULL);
7878     }
7879 }
7880
7881 int
7882 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7883 {   // front-end-free part taken out of PieceMenuPopup
7884     int whichMenu; int xSqr, ySqr;
7885
7886     if(seekGraphUp) { // [HGM] seekgraph
7887         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7888         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7889         return -2;
7890     }
7891
7892     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7893          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7894         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7895         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7896         if(action == Press)   {
7897             originalFlip = flipView;
7898             flipView = !flipView; // temporarily flip board to see game from partners perspective
7899             DrawPosition(TRUE, partnerBoard);
7900             DisplayMessage(partnerStatus, "");
7901             partnerUp = TRUE;
7902         } else if(action == Release) {
7903             flipView = originalFlip;
7904             DrawPosition(TRUE, boards[currentMove]);
7905             partnerUp = FALSE;
7906         }
7907         return -2;
7908     }
7909
7910     xSqr = EventToSquare(x, BOARD_WIDTH);
7911     ySqr = EventToSquare(y, BOARD_HEIGHT);
7912     if (action == Release) {
7913         if(pieceSweep != EmptySquare) {
7914             EditPositionMenuEvent(pieceSweep, toX, toY);
7915             pieceSweep = EmptySquare;
7916         } else UnLoadPV(); // [HGM] pv
7917     }
7918     if (action != Press) return -2; // return code to be ignored
7919     switch (gameMode) {
7920       case IcsExamining:
7921         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7922       case EditPosition:
7923         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7924         if (xSqr < 0 || ySqr < 0) return -1;
7925         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7926         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7927         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7928         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7929         NextPiece(0);
7930         return 2; // grab
7931       case IcsObserving:
7932         if(!appData.icsEngineAnalyze) return -1;
7933       case IcsPlayingWhite:
7934       case IcsPlayingBlack:
7935         if(!appData.zippyPlay) goto noZip;
7936       case AnalyzeMode:
7937       case AnalyzeFile:
7938       case MachinePlaysWhite:
7939       case MachinePlaysBlack:
7940       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7941         if (!appData.dropMenu) {
7942           LoadPV(x, y);
7943           return 2; // flag front-end to grab mouse events
7944         }
7945         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7946            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7947       case EditGame:
7948       noZip:
7949         if (xSqr < 0 || ySqr < 0) return -1;
7950         if (!appData.dropMenu || appData.testLegality &&
7951             gameInfo.variant != VariantBughouse &&
7952             gameInfo.variant != VariantCrazyhouse) return -1;
7953         whichMenu = 1; // drop menu
7954         break;
7955       default:
7956         return -1;
7957     }
7958
7959     if (((*fromX = xSqr) < 0) ||
7960         ((*fromY = ySqr) < 0)) {
7961         *fromX = *fromY = -1;
7962         return -1;
7963     }
7964     if (flipView)
7965       *fromX = BOARD_WIDTH - 1 - *fromX;
7966     else
7967       *fromY = BOARD_HEIGHT - 1 - *fromY;
7968
7969     return whichMenu;
7970 }
7971
7972 void
7973 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7974 {
7975 //    char * hint = lastHint;
7976     FrontEndProgramStats stats;
7977
7978     stats.which = cps == &first ? 0 : 1;
7979     stats.depth = cpstats->depth;
7980     stats.nodes = cpstats->nodes;
7981     stats.score = cpstats->score;
7982     stats.time = cpstats->time;
7983     stats.pv = cpstats->movelist;
7984     stats.hint = lastHint;
7985     stats.an_move_index = 0;
7986     stats.an_move_count = 0;
7987
7988     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7989         stats.hint = cpstats->move_name;
7990         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7991         stats.an_move_count = cpstats->nr_moves;
7992     }
7993
7994     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
7995
7996     SetProgramStats( &stats );
7997 }
7998
7999 void
8000 ClearEngineOutputPane (int which)
8001 {
8002     static FrontEndProgramStats dummyStats;
8003     dummyStats.which = which;
8004     dummyStats.pv = "#";
8005     SetProgramStats( &dummyStats );
8006 }
8007
8008 #define MAXPLAYERS 500
8009
8010 char *
8011 TourneyStandings (int display)
8012 {
8013     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8014     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8015     char result, *p, *names[MAXPLAYERS];
8016
8017     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8018         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8019     names[0] = p = strdup(appData.participants);
8020     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8021
8022     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8023
8024     while(result = appData.results[nr]) {
8025         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8026         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8027         wScore = bScore = 0;
8028         switch(result) {
8029           case '+': wScore = 2; break;
8030           case '-': bScore = 2; break;
8031           case '=': wScore = bScore = 1; break;
8032           case ' ':
8033           case '*': return strdup("busy"); // tourney not finished
8034         }
8035         score[w] += wScore;
8036         score[b] += bScore;
8037         games[w]++;
8038         games[b]++;
8039         nr++;
8040     }
8041     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8042     for(w=0; w<nPlayers; w++) {
8043         bScore = -1;
8044         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8045         ranking[w] = b; points[w] = bScore; score[b] = -2;
8046     }
8047     p = malloc(nPlayers*34+1);
8048     for(w=0; w<nPlayers && w<display; w++)
8049         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8050     free(names[0]);
8051     return p;
8052 }
8053
8054 void
8055 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8056 {       // count all piece types
8057         int p, f, r;
8058         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8059         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8060         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8061                 p = board[r][f];
8062                 pCnt[p]++;
8063                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8064                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8065                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8066                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8067                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8068                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8069         }
8070 }
8071
8072 int
8073 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8074 {
8075         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8076         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8077
8078         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8079         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8080         if(myPawns == 2 && nMine == 3) // KPP
8081             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8082         if(myPawns == 1 && nMine == 2) // KP
8083             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8084         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8085             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8086         if(myPawns) return FALSE;
8087         if(pCnt[WhiteRook+side])
8088             return pCnt[BlackRook-side] ||
8089                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8090                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8091                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8092         if(pCnt[WhiteCannon+side]) {
8093             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8094             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8095         }
8096         if(pCnt[WhiteKnight+side])
8097             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8098         return FALSE;
8099 }
8100
8101 int
8102 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8103 {
8104         VariantClass v = gameInfo.variant;
8105
8106         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8107         if(v == VariantShatranj) return TRUE; // always winnable through baring
8108         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8109         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8110
8111         if(v == VariantXiangqi) {
8112                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8113
8114                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8115                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8116                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8117                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8118                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8119                 if(stale) // we have at least one last-rank P plus perhaps C
8120                     return majors // KPKX
8121                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8122                 else // KCA*E*
8123                     return pCnt[WhiteFerz+side] // KCAK
8124                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8125                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8126                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8127
8128         } else if(v == VariantKnightmate) {
8129                 if(nMine == 1) return FALSE;
8130                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8131         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8132                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8133
8134                 if(nMine == 1) return FALSE; // bare King
8135                 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
8136                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8137                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8138                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8139                 if(pCnt[WhiteKnight+side])
8140                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8141                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8142                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8143                 if(nBishops)
8144                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8145                 if(pCnt[WhiteAlfil+side])
8146                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8147                 if(pCnt[WhiteWazir+side])
8148                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8149         }
8150
8151         return TRUE;
8152 }
8153
8154 int
8155 CompareWithRights (Board b1, Board b2)
8156 {
8157     int rights = 0;
8158     if(!CompareBoards(b1, b2)) return FALSE;
8159     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8160     /* compare castling rights */
8161     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8162            rights++; /* King lost rights, while rook still had them */
8163     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8164         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8165            rights++; /* but at least one rook lost them */
8166     }
8167     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8168            rights++;
8169     if( b1[CASTLING][5] != NoRights ) {
8170         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8171            rights++;
8172     }
8173     return rights == 0;
8174 }
8175
8176 int
8177 Adjudicate (ChessProgramState *cps)
8178 {       // [HGM] some adjudications useful with buggy engines
8179         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8180         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8181         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8182         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8183         int k, drop, count = 0; static int bare = 1;
8184         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8185         Boolean canAdjudicate = !appData.icsActive;
8186
8187         // most tests only when we understand the game, i.e. legality-checking on
8188             if( appData.testLegality )
8189             {   /* [HGM] Some more adjudications for obstinate engines */
8190                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8191                 static int moveCount = 6;
8192                 ChessMove result;
8193                 char *reason = NULL;
8194
8195                 /* Count what is on board. */
8196                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8197
8198                 /* Some material-based adjudications that have to be made before stalemate test */
8199                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8200                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8201                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8202                      if(canAdjudicate && appData.checkMates) {
8203                          if(engineOpponent)
8204                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8205                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8206                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8207                          return 1;
8208                      }
8209                 }
8210
8211                 /* Bare King in Shatranj (loses) or Losers (wins) */
8212                 if( nrW == 1 || nrB == 1) {
8213                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8214                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8215                      if(canAdjudicate && appData.checkMates) {
8216                          if(engineOpponent)
8217                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8218                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8219                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8220                          return 1;
8221                      }
8222                   } else
8223                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8224                   {    /* bare King */
8225                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8226                         if(canAdjudicate && appData.checkMates) {
8227                             /* but only adjudicate if adjudication enabled */
8228                             if(engineOpponent)
8229                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8230                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8231                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8232                             return 1;
8233                         }
8234                   }
8235                 } else bare = 1;
8236
8237
8238             // don't wait for engine to announce game end if we can judge ourselves
8239             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8240               case MT_CHECK:
8241                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8242                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8243                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8244                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8245                             checkCnt++;
8246                         if(checkCnt >= 2) {
8247                             reason = "Xboard adjudication: 3rd check";
8248                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8249                             break;
8250                         }
8251                     }
8252                 }
8253               case MT_NONE:
8254               default:
8255                 break;
8256               case MT_STEALMATE:
8257               case MT_STALEMATE:
8258               case MT_STAINMATE:
8259                 reason = "Xboard adjudication: Stalemate";
8260                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8261                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8262                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8263                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8264                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8265                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8266                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8267                                                                         EP_CHECKMATE : EP_WINS);
8268                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8269                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8270                 }
8271                 break;
8272               case MT_CHECKMATE:
8273                 reason = "Xboard adjudication: Checkmate";
8274                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8275                 if(gameInfo.variant == VariantShogi) {
8276                     if(forwardMostMove > backwardMostMove
8277                        && moveList[forwardMostMove-1][1] == '@'
8278                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8279                         reason = "XBoard adjudication: pawn-drop mate";
8280                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8281                     }
8282                 }
8283                 break;
8284             }
8285
8286                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8287                     case EP_STALEMATE:
8288                         result = GameIsDrawn; break;
8289                     case EP_CHECKMATE:
8290                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8291                     case EP_WINS:
8292                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8293                     default:
8294                         result = EndOfFile;
8295                 }
8296                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8297                     if(engineOpponent)
8298                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8299                     GameEnds( result, reason, GE_XBOARD );
8300                     return 1;
8301                 }
8302
8303                 /* Next absolutely insufficient mating material. */
8304                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8305                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8306                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8307
8308                      /* always flag draws, for judging claims */
8309                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8310
8311                      if(canAdjudicate && appData.materialDraws) {
8312                          /* but only adjudicate them if adjudication enabled */
8313                          if(engineOpponent) {
8314                            SendToProgram("force\n", engineOpponent); // suppress reply
8315                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8316                          }
8317                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8318                          return 1;
8319                      }
8320                 }
8321
8322                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8323                 if(gameInfo.variant == VariantXiangqi ?
8324                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8325                  : nrW + nrB == 4 &&
8326                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8327                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8328                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8329                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8330                    ) ) {
8331                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8332                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8333                           if(engineOpponent) {
8334                             SendToProgram("force\n", engineOpponent); // suppress reply
8335                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8336                           }
8337                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8338                           return 1;
8339                      }
8340                 } else moveCount = 6;
8341             }
8342
8343         // Repetition draws and 50-move rule can be applied independently of legality testing
8344
8345                 /* Check for rep-draws */
8346                 count = 0;
8347                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8348                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8349                 for(k = forwardMostMove-2;
8350                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8351                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8352                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8353                     k-=2)
8354                 {   int rights=0;
8355                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8356                         /* compare castling rights */
8357                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8358                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8359                                 rights++; /* King lost rights, while rook still had them */
8360                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8361                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8362                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8363                                    rights++; /* but at least one rook lost them */
8364                         }
8365                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8366                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8367                                 rights++;
8368                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8369                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8370                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8371                                    rights++;
8372                         }
8373                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8374                             && appData.drawRepeats > 1) {
8375                              /* adjudicate after user-specified nr of repeats */
8376                              int result = GameIsDrawn;
8377                              char *details = "XBoard adjudication: repetition draw";
8378                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8379                                 // [HGM] xiangqi: check for forbidden perpetuals
8380                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8381                                 for(m=forwardMostMove; m>k; m-=2) {
8382                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8383                                         ourPerpetual = 0; // the current mover did not always check
8384                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8385                                         hisPerpetual = 0; // the opponent did not always check
8386                                 }
8387                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8388                                                                         ourPerpetual, hisPerpetual);
8389                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8390                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8391                                     details = "Xboard adjudication: perpetual checking";
8392                                 } else
8393                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8394                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8395                                 } else
8396                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8397                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8398                                         result = BlackWins;
8399                                         details = "Xboard adjudication: repetition";
8400                                     }
8401                                 } else // it must be XQ
8402                                 // Now check for perpetual chases
8403                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8404                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8405                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8406                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8407                                         static char resdet[MSG_SIZ];
8408                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8409                                         details = resdet;
8410                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8411                                     } else
8412                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8413                                         break; // Abort repetition-checking loop.
8414                                 }
8415                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8416                              }
8417                              if(engineOpponent) {
8418                                SendToProgram("force\n", engineOpponent); // suppress reply
8419                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8420                              }
8421                              GameEnds( result, details, GE_XBOARD );
8422                              return 1;
8423                         }
8424                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8425                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8426                     }
8427                 }
8428
8429                 /* Now we test for 50-move draws. Determine ply count */
8430                 count = forwardMostMove;
8431                 /* look for last irreversble move */
8432                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8433                     count--;
8434                 /* if we hit starting position, add initial plies */
8435                 if( count == backwardMostMove )
8436                     count -= initialRulePlies;
8437                 count = forwardMostMove - count;
8438                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8439                         // adjust reversible move counter for checks in Xiangqi
8440                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8441                         if(i < backwardMostMove) i = backwardMostMove;
8442                         while(i <= forwardMostMove) {
8443                                 lastCheck = inCheck; // check evasion does not count
8444                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8445                                 if(inCheck || lastCheck) count--; // check does not count
8446                                 i++;
8447                         }
8448                 }
8449                 if( count >= 100)
8450                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8451                          /* this is used to judge if draw claims are legal */
8452                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8453                          if(engineOpponent) {
8454                            SendToProgram("force\n", engineOpponent); // suppress reply
8455                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8456                          }
8457                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8458                          return 1;
8459                 }
8460
8461                 /* if draw offer is pending, treat it as a draw claim
8462                  * when draw condition present, to allow engines a way to
8463                  * claim draws before making their move to avoid a race
8464                  * condition occurring after their move
8465                  */
8466                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8467                          char *p = NULL;
8468                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8469                              p = "Draw claim: 50-move rule";
8470                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8471                              p = "Draw claim: 3-fold repetition";
8472                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8473                              p = "Draw claim: insufficient mating material";
8474                          if( p != NULL && canAdjudicate) {
8475                              if(engineOpponent) {
8476                                SendToProgram("force\n", engineOpponent); // suppress reply
8477                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8478                              }
8479                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8480                              return 1;
8481                          }
8482                 }
8483
8484                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8485                     if(engineOpponent) {
8486                       SendToProgram("force\n", engineOpponent); // suppress reply
8487                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8488                     }
8489                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8490                     return 1;
8491                 }
8492         return 0;
8493 }
8494
8495 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8496 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8497 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8498
8499 static int
8500 BitbaseProbe ()
8501 {
8502     int pieces[10], squares[10], cnt=0, r, f, res;
8503     static int loaded;
8504     static PPROBE_EGBB probeBB;
8505     if(!appData.testLegality) return 10;
8506     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8507     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8508     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8509     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8510         ChessSquare piece = boards[forwardMostMove][r][f];
8511         int black = (piece >= BlackPawn);
8512         int type = piece - black*BlackPawn;
8513         if(piece == EmptySquare) continue;
8514         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8515         if(type == WhiteKing) type = WhiteQueen + 1;
8516         type = egbbCode[type];
8517         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8518         pieces[cnt] = type + black*6;
8519         if(++cnt > 5) return 11;
8520     }
8521     pieces[cnt] = squares[cnt] = 0;
8522     // probe EGBB
8523     if(loaded == 2) return 13; // loading failed before
8524     if(loaded == 0) {
8525         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8526         HMODULE lib;
8527         PLOAD_EGBB loadBB;
8528         loaded = 2; // prepare for failure
8529         if(!path) return 13; // no egbb installed
8530         strncpy(buf, path + 8, MSG_SIZ);
8531         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8532         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8533         lib = LoadLibrary(buf);
8534         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8535         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8536         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8537         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8538         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8539         loaded = 1; // success!
8540     }
8541     res = probeBB(forwardMostMove & 1, pieces, squares);
8542     return res > 0 ? 1 : res < 0 ? -1 : 0;
8543 }
8544
8545 char *
8546 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8547 {   // [HGM] book: this routine intercepts moves to simulate book replies
8548     char *bookHit = NULL;
8549
8550     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8551         char buf[MSG_SIZ];
8552         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8553         SendToProgram(buf, cps);
8554     }
8555     //first determine if the incoming move brings opponent into his book
8556     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8557         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8558     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8559     if(bookHit != NULL && !cps->bookSuspend) {
8560         // make sure opponent is not going to reply after receiving move to book position
8561         SendToProgram("force\n", cps);
8562         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8563     }
8564     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8565     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8566     // now arrange restart after book miss
8567     if(bookHit) {
8568         // after a book hit we never send 'go', and the code after the call to this routine
8569         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8570         char buf[MSG_SIZ], *move = bookHit;
8571         if(cps->useSAN) {
8572             int fromX, fromY, toX, toY;
8573             char promoChar;
8574             ChessMove moveType;
8575             move = buf + 30;
8576             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8577                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8578                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8579                                     PosFlags(forwardMostMove),
8580                                     fromY, fromX, toY, toX, promoChar, move);
8581             } else {
8582                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8583                 bookHit = NULL;
8584             }
8585         }
8586         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8587         SendToProgram(buf, cps);
8588         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8589     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8590         SendToProgram("go\n", cps);
8591         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8592     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8593         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8594             SendToProgram("go\n", cps);
8595         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8596     }
8597     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8598 }
8599
8600 int
8601 LoadError (char *errmess, ChessProgramState *cps)
8602 {   // unloads engine and switches back to -ncp mode if it was first
8603     if(cps->initDone) return FALSE;
8604     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8605     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8606     cps->pr = NoProc;
8607     if(cps == &first) {
8608         appData.noChessProgram = TRUE;
8609         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8610         gameMode = BeginningOfGame; ModeHighlight();
8611         SetNCPMode();
8612     }
8613     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8614     DisplayMessage("", ""); // erase waiting message
8615     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8616     return TRUE;
8617 }
8618
8619 char *savedMessage;
8620 ChessProgramState *savedState;
8621 void
8622 DeferredBookMove (void)
8623 {
8624         if(savedState->lastPing != savedState->lastPong)
8625                     ScheduleDelayedEvent(DeferredBookMove, 10);
8626         else
8627         HandleMachineMove(savedMessage, savedState);
8628 }
8629
8630 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8631 static ChessProgramState *stalledEngine;
8632 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8633
8634 void
8635 HandleMachineMove (char *message, ChessProgramState *cps)
8636 {
8637     static char firstLeg[20];
8638     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8639     char realname[MSG_SIZ];
8640     int fromX, fromY, toX, toY;
8641     ChessMove moveType;
8642     char promoChar, roar;
8643     char *p, *pv=buf1;
8644     int oldError;
8645     char *bookHit;
8646
8647     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8648         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8649         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8650             DisplayError(_("Invalid pairing from pairing engine"), 0);
8651             return;
8652         }
8653         pairingReceived = 1;
8654         NextMatchGame();
8655         return; // Skim the pairing messages here.
8656     }
8657
8658     oldError = cps->userError; cps->userError = 0;
8659
8660 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8661     /*
8662      * Kludge to ignore BEL characters
8663      */
8664     while (*message == '\007') message++;
8665
8666     /*
8667      * [HGM] engine debug message: ignore lines starting with '#' character
8668      */
8669     if(cps->debug && *message == '#') return;
8670
8671     /*
8672      * Look for book output
8673      */
8674     if (cps == &first && bookRequested) {
8675         if (message[0] == '\t' || message[0] == ' ') {
8676             /* Part of the book output is here; append it */
8677             strcat(bookOutput, message);
8678             strcat(bookOutput, "  \n");
8679             return;
8680         } else if (bookOutput[0] != NULLCHAR) {
8681             /* All of book output has arrived; display it */
8682             char *p = bookOutput;
8683             while (*p != NULLCHAR) {
8684                 if (*p == '\t') *p = ' ';
8685                 p++;
8686             }
8687             DisplayInformation(bookOutput);
8688             bookRequested = FALSE;
8689             /* Fall through to parse the current output */
8690         }
8691     }
8692
8693     /*
8694      * Look for machine move.
8695      */
8696     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8697         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8698     {
8699         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8700             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8701             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8702             stalledEngine = cps;
8703             if(appData.ponderNextMove) { // bring opponent out of ponder
8704                 if(gameMode == TwoMachinesPlay) {
8705                     if(cps->other->pause)
8706                         PauseEngine(cps->other);
8707                     else
8708                         SendToProgram("easy\n", cps->other);
8709                 }
8710             }
8711             StopClocks();
8712             return;
8713         }
8714
8715       if(cps->usePing) {
8716
8717         /* This method is only useful on engines that support ping */
8718         if(abortEngineThink) {
8719             if (appData.debugMode) {
8720                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8721             }
8722             SendToProgram("undo\n", cps);
8723             return;
8724         }
8725
8726         if (cps->lastPing != cps->lastPong) {
8727             /* Extra move from before last new; ignore */
8728             if (appData.debugMode) {
8729                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8730             }
8731           return;
8732         }
8733
8734       } else {
8735
8736         int machineWhite = FALSE;
8737
8738         switch (gameMode) {
8739           case BeginningOfGame:
8740             /* Extra move from before last reset; ignore */
8741             if (appData.debugMode) {
8742                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8743             }
8744             return;
8745
8746           case EndOfGame:
8747           case IcsIdle:
8748           default:
8749             /* Extra move after we tried to stop.  The mode test is
8750                not a reliable way of detecting this problem, but it's
8751                the best we can do on engines that don't support ping.
8752             */
8753             if (appData.debugMode) {
8754                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8755                         cps->which, gameMode);
8756             }
8757             SendToProgram("undo\n", cps);
8758             return;
8759
8760           case MachinePlaysWhite:
8761           case IcsPlayingWhite:
8762             machineWhite = TRUE;
8763             break;
8764
8765           case MachinePlaysBlack:
8766           case IcsPlayingBlack:
8767             machineWhite = FALSE;
8768             break;
8769
8770           case TwoMachinesPlay:
8771             machineWhite = (cps->twoMachinesColor[0] == 'w');
8772             break;
8773         }
8774         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8775             if (appData.debugMode) {
8776                 fprintf(debugFP,
8777                         "Ignoring move out of turn by %s, gameMode %d"
8778                         ", forwardMost %d\n",
8779                         cps->which, gameMode, forwardMostMove);
8780             }
8781             return;
8782         }
8783       }
8784
8785         if(cps->alphaRank) AlphaRank(machineMove, 4);
8786
8787         // [HGM] lion: (some very limited) support for Alien protocol
8788         killX = killY = kill2X = kill2Y = -1;
8789         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8790             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8791             return;
8792         }
8793         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8794             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8795             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8796         }
8797         if(firstLeg[0]) { // there was a previous leg;
8798             // only support case where same piece makes two step
8799             char buf[20], *p = machineMove+1, *q = buf+1, f;
8800             safeStrCpy(buf, machineMove, 20);
8801             while(isdigit(*q)) q++; // find start of to-square
8802             safeStrCpy(machineMove, firstLeg, 20);
8803             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8804             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8805             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8806             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8807             firstLeg[0] = NULLCHAR;
8808         }
8809
8810         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8811                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8812             /* Machine move could not be parsed; ignore it. */
8813           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8814                     machineMove, _(cps->which));
8815             DisplayMoveError(buf1);
8816             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8817                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8818             if (gameMode == TwoMachinesPlay) {
8819               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8820                        buf1, GE_XBOARD);
8821             }
8822             return;
8823         }
8824
8825         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8826         /* So we have to redo legality test with true e.p. status here,  */
8827         /* to make sure an illegal e.p. capture does not slip through,   */
8828         /* to cause a forfeit on a justified illegal-move complaint      */
8829         /* of the opponent.                                              */
8830         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8831            ChessMove moveType;
8832            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8833                              fromY, fromX, toY, toX, promoChar);
8834             if(moveType == IllegalMove) {
8835               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8836                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8837                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8838                            buf1, GE_XBOARD);
8839                 return;
8840            } else if(!appData.fischerCastling)
8841            /* [HGM] Kludge to handle engines that send FRC-style castling
8842               when they shouldn't (like TSCP-Gothic) */
8843            switch(moveType) {
8844              case WhiteASideCastleFR:
8845              case BlackASideCastleFR:
8846                toX+=2;
8847                currentMoveString[2]++;
8848                break;
8849              case WhiteHSideCastleFR:
8850              case BlackHSideCastleFR:
8851                toX--;
8852                currentMoveString[2]--;
8853                break;
8854              default: ; // nothing to do, but suppresses warning of pedantic compilers
8855            }
8856         }
8857         hintRequested = FALSE;
8858         lastHint[0] = NULLCHAR;
8859         bookRequested = FALSE;
8860         /* Program may be pondering now */
8861         cps->maybeThinking = TRUE;
8862         if (cps->sendTime == 2) cps->sendTime = 1;
8863         if (cps->offeredDraw) cps->offeredDraw--;
8864
8865         /* [AS] Save move info*/
8866         pvInfoList[ forwardMostMove ].score = programStats.score;
8867         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8868         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8869
8870         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8871
8872         /* Test suites abort the 'game' after one move */
8873         if(*appData.finger) {
8874            static FILE *f;
8875            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8876            if(!f) f = fopen(appData.finger, "w");
8877            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8878            else { DisplayFatalError("Bad output file", errno, 0); return; }
8879            free(fen);
8880            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8881         }
8882         if(appData.epd) {
8883            if(solvingTime >= 0) {
8884               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8885               totalTime += solvingTime; first.matchWins++;
8886            } else {
8887               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8888               second.matchWins++;
8889            }
8890            OutputKibitz(2, buf1);
8891            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8892         }
8893
8894         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8895         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8896             int count = 0;
8897
8898             while( count < adjudicateLossPlies ) {
8899                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8900
8901                 if( count & 1 ) {
8902                     score = -score; /* Flip score for winning side */
8903                 }
8904
8905                 if( score > appData.adjudicateLossThreshold ) {
8906                     break;
8907                 }
8908
8909                 count++;
8910             }
8911
8912             if( count >= adjudicateLossPlies ) {
8913                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8914
8915                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8916                     "Xboard adjudication",
8917                     GE_XBOARD );
8918
8919                 return;
8920             }
8921         }
8922
8923         if(Adjudicate(cps)) {
8924             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8925             return; // [HGM] adjudicate: for all automatic game ends
8926         }
8927
8928 #if ZIPPY
8929         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8930             first.initDone) {
8931           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8932                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8933                 SendToICS("draw ");
8934                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8935           }
8936           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8937           ics_user_moved = 1;
8938           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8939                 char buf[3*MSG_SIZ];
8940
8941                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8942                         programStats.score / 100.,
8943                         programStats.depth,
8944                         programStats.time / 100.,
8945                         (unsigned int)programStats.nodes,
8946                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8947                         programStats.movelist);
8948                 SendToICS(buf);
8949           }
8950         }
8951 #endif
8952
8953         /* [AS] Clear stats for next move */
8954         ClearProgramStats();
8955         thinkOutput[0] = NULLCHAR;
8956         hiddenThinkOutputState = 0;
8957
8958         bookHit = NULL;
8959         if (gameMode == TwoMachinesPlay) {
8960             /* [HGM] relaying draw offers moved to after reception of move */
8961             /* and interpreting offer as claim if it brings draw condition */
8962             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8963                 SendToProgram("draw\n", cps->other);
8964             }
8965             if (cps->other->sendTime) {
8966                 SendTimeRemaining(cps->other,
8967                                   cps->other->twoMachinesColor[0] == 'w');
8968             }
8969             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8970             if (firstMove && !bookHit) {
8971                 firstMove = FALSE;
8972                 if (cps->other->useColors) {
8973                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8974                 }
8975                 SendToProgram("go\n", cps->other);
8976             }
8977             cps->other->maybeThinking = TRUE;
8978         }
8979
8980         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8981
8982         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8983
8984         if (!pausing && appData.ringBellAfterMoves) {
8985             if(!roar) RingBell();
8986         }
8987
8988         /*
8989          * Reenable menu items that were disabled while
8990          * machine was thinking
8991          */
8992         if (gameMode != TwoMachinesPlay)
8993             SetUserThinkingEnables();
8994
8995         // [HGM] book: after book hit opponent has received move and is now in force mode
8996         // force the book reply into it, and then fake that it outputted this move by jumping
8997         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8998         if(bookHit) {
8999                 static char bookMove[MSG_SIZ]; // a bit generous?
9000
9001                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9002                 strcat(bookMove, bookHit);
9003                 message = bookMove;
9004                 cps = cps->other;
9005                 programStats.nodes = programStats.depth = programStats.time =
9006                 programStats.score = programStats.got_only_move = 0;
9007                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9008
9009                 if(cps->lastPing != cps->lastPong) {
9010                     savedMessage = message; // args for deferred call
9011                     savedState = cps;
9012                     ScheduleDelayedEvent(DeferredBookMove, 10);
9013                     return;
9014                 }
9015                 goto FakeBookMove;
9016         }
9017
9018         return;
9019     }
9020
9021     /* Set special modes for chess engines.  Later something general
9022      *  could be added here; for now there is just one kludge feature,
9023      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9024      *  when "xboard" is given as an interactive command.
9025      */
9026     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9027         cps->useSigint = FALSE;
9028         cps->useSigterm = FALSE;
9029     }
9030     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9031       ParseFeatures(message+8, cps);
9032       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9033     }
9034
9035     if (!strncmp(message, "setup ", 6) && 
9036         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9037           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9038                                         ) { // [HGM] allow first engine to define opening position
9039       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9040       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9041       *buf = NULLCHAR;
9042       if(sscanf(message, "setup (%s", buf) == 1) {
9043         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9044         ASSIGN(appData.pieceToCharTable, buf);
9045       }
9046       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9047       if(dummy >= 3) {
9048         while(message[s] && message[s++] != ' ');
9049         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9050            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9051             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9052             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9053           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9054           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9055           startedFromSetupPosition = FALSE;
9056         }
9057       }
9058       if(startedFromSetupPosition) return;
9059       ParseFEN(boards[0], &dummy, message+s, FALSE);
9060       DrawPosition(TRUE, boards[0]);
9061       CopyBoard(initialPosition, boards[0]);
9062       startedFromSetupPosition = TRUE;
9063       return;
9064     }
9065     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9066       ChessSquare piece = WhitePawn;
9067       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9068       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9069       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9070       piece += CharToPiece(ID & 255) - WhitePawn;
9071       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9072       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9073       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9074       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9075       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9076       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9077                                                && gameInfo.variant != VariantGreat
9078                                                && gameInfo.variant != VariantFairy    ) return;
9079       if(piece < EmptySquare) {
9080         pieceDefs = TRUE;
9081         ASSIGN(pieceDesc[piece], buf1);
9082         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9083       }
9084       return;
9085     }
9086     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9087       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9088       Sweep(0);
9089       return;
9090     }
9091     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9092      * want this, I was asked to put it in, and obliged.
9093      */
9094     if (!strncmp(message, "setboard ", 9)) {
9095         Board initial_position;
9096
9097         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9098
9099         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9100             DisplayError(_("Bad FEN received from engine"), 0);
9101             return ;
9102         } else {
9103            Reset(TRUE, FALSE);
9104            CopyBoard(boards[0], initial_position);
9105            initialRulePlies = FENrulePlies;
9106            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9107            else gameMode = MachinePlaysBlack;
9108            DrawPosition(FALSE, boards[currentMove]);
9109         }
9110         return;
9111     }
9112
9113     /*
9114      * Look for communication commands
9115      */
9116     if (!strncmp(message, "telluser ", 9)) {
9117         if(message[9] == '\\' && message[10] == '\\')
9118             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9119         PlayTellSound();
9120         DisplayNote(message + 9);
9121         return;
9122     }
9123     if (!strncmp(message, "tellusererror ", 14)) {
9124         cps->userError = 1;
9125         if(message[14] == '\\' && message[15] == '\\')
9126             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9127         PlayTellSound();
9128         DisplayError(message + 14, 0);
9129         return;
9130     }
9131     if (!strncmp(message, "tellopponent ", 13)) {
9132       if (appData.icsActive) {
9133         if (loggedOn) {
9134           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9135           SendToICS(buf1);
9136         }
9137       } else {
9138         DisplayNote(message + 13);
9139       }
9140       return;
9141     }
9142     if (!strncmp(message, "tellothers ", 11)) {
9143       if (appData.icsActive) {
9144         if (loggedOn) {
9145           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9146           SendToICS(buf1);
9147         }
9148       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9149       return;
9150     }
9151     if (!strncmp(message, "tellall ", 8)) {
9152       if (appData.icsActive) {
9153         if (loggedOn) {
9154           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9155           SendToICS(buf1);
9156         }
9157       } else {
9158         DisplayNote(message + 8);
9159       }
9160       return;
9161     }
9162     if (strncmp(message, "warning", 7) == 0) {
9163         /* Undocumented feature, use tellusererror in new code */
9164         DisplayError(message, 0);
9165         return;
9166     }
9167     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9168         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9169         strcat(realname, " query");
9170         AskQuestion(realname, buf2, buf1, cps->pr);
9171         return;
9172     }
9173     /* Commands from the engine directly to ICS.  We don't allow these to be
9174      *  sent until we are logged on. Crafty kibitzes have been known to
9175      *  interfere with the login process.
9176      */
9177     if (loggedOn) {
9178         if (!strncmp(message, "tellics ", 8)) {
9179             SendToICS(message + 8);
9180             SendToICS("\n");
9181             return;
9182         }
9183         if (!strncmp(message, "tellicsnoalias ", 15)) {
9184             SendToICS(ics_prefix);
9185             SendToICS(message + 15);
9186             SendToICS("\n");
9187             return;
9188         }
9189         /* The following are for backward compatibility only */
9190         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9191             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9192             SendToICS(ics_prefix);
9193             SendToICS(message);
9194             SendToICS("\n");
9195             return;
9196         }
9197     }
9198     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9199         if(initPing == cps->lastPong) {
9200             if(gameInfo.variant == VariantUnknown) {
9201                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9202                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9203                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9204             }
9205             initPing = -1;
9206         }
9207         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9208             abortEngineThink = FALSE;
9209             DisplayMessage("", "");
9210             ThawUI();
9211         }
9212         return;
9213     }
9214     if(!strncmp(message, "highlight ", 10)) {
9215         if(appData.testLegality && !*engineVariant && appData.markers) return;
9216         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9217         return;
9218     }
9219     if(!strncmp(message, "click ", 6)) {
9220         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9221         if(appData.testLegality || !appData.oneClick) return;
9222         sscanf(message+6, "%c%d%c", &f, &y, &c);
9223         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9224         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9225         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9226         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9227         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9228         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9229             LeftClick(Release, lastLeftX, lastLeftY);
9230         controlKey  = (c == ',');
9231         LeftClick(Press, x, y);
9232         LeftClick(Release, x, y);
9233         first.highlight = f;
9234         return;
9235     }
9236     /*
9237      * If the move is illegal, cancel it and redraw the board.
9238      * Also deal with other error cases.  Matching is rather loose
9239      * here to accommodate engines written before the spec.
9240      */
9241     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9242         strncmp(message, "Error", 5) == 0) {
9243         if (StrStr(message, "name") ||
9244             StrStr(message, "rating") || StrStr(message, "?") ||
9245             StrStr(message, "result") || StrStr(message, "board") ||
9246             StrStr(message, "bk") || StrStr(message, "computer") ||
9247             StrStr(message, "variant") || StrStr(message, "hint") ||
9248             StrStr(message, "random") || StrStr(message, "depth") ||
9249             StrStr(message, "accepted")) {
9250             return;
9251         }
9252         if (StrStr(message, "protover")) {
9253           /* Program is responding to input, so it's apparently done
9254              initializing, and this error message indicates it is
9255              protocol version 1.  So we don't need to wait any longer
9256              for it to initialize and send feature commands. */
9257           FeatureDone(cps, 1);
9258           cps->protocolVersion = 1;
9259           return;
9260         }
9261         cps->maybeThinking = FALSE;
9262
9263         if (StrStr(message, "draw")) {
9264             /* Program doesn't have "draw" command */
9265             cps->sendDrawOffers = 0;
9266             return;
9267         }
9268         if (cps->sendTime != 1 &&
9269             (StrStr(message, "time") || StrStr(message, "otim"))) {
9270           /* Program apparently doesn't have "time" or "otim" command */
9271           cps->sendTime = 0;
9272           return;
9273         }
9274         if (StrStr(message, "analyze")) {
9275             cps->analysisSupport = FALSE;
9276             cps->analyzing = FALSE;
9277 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9278             EditGameEvent(); // [HGM] try to preserve loaded game
9279             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9280             DisplayError(buf2, 0);
9281             return;
9282         }
9283         if (StrStr(message, "(no matching move)st")) {
9284           /* Special kludge for GNU Chess 4 only */
9285           cps->stKludge = TRUE;
9286           SendTimeControl(cps, movesPerSession, timeControl,
9287                           timeIncrement, appData.searchDepth,
9288                           searchTime);
9289           return;
9290         }
9291         if (StrStr(message, "(no matching move)sd")) {
9292           /* Special kludge for GNU Chess 4 only */
9293           cps->sdKludge = TRUE;
9294           SendTimeControl(cps, movesPerSession, timeControl,
9295                           timeIncrement, appData.searchDepth,
9296                           searchTime);
9297           return;
9298         }
9299         if (!StrStr(message, "llegal")) {
9300             return;
9301         }
9302         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9303             gameMode == IcsIdle) return;
9304         if (forwardMostMove <= backwardMostMove) return;
9305         if (pausing) PauseEvent();
9306       if(appData.forceIllegal) {
9307             // [HGM] illegal: machine refused move; force position after move into it
9308           SendToProgram("force\n", cps);
9309           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9310                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9311                 // when black is to move, while there might be nothing on a2 or black
9312                 // might already have the move. So send the board as if white has the move.
9313                 // But first we must change the stm of the engine, as it refused the last move
9314                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9315                 if(WhiteOnMove(forwardMostMove)) {
9316                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9317                     SendBoard(cps, forwardMostMove); // kludgeless board
9318                 } else {
9319                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9320                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9321                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9322                 }
9323           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9324             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9325                  gameMode == TwoMachinesPlay)
9326               SendToProgram("go\n", cps);
9327             return;
9328       } else
9329         if (gameMode == PlayFromGameFile) {
9330             /* Stop reading this game file */
9331             gameMode = EditGame;
9332             ModeHighlight();
9333         }
9334         /* [HGM] illegal-move claim should forfeit game when Xboard */
9335         /* only passes fully legal moves                            */
9336         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9337             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9338                                 "False illegal-move claim", GE_XBOARD );
9339             return; // do not take back move we tested as valid
9340         }
9341         currentMove = forwardMostMove-1;
9342         DisplayMove(currentMove-1); /* before DisplayMoveError */
9343         SwitchClocks(forwardMostMove-1); // [HGM] race
9344         DisplayBothClocks();
9345         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9346                 parseList[currentMove], _(cps->which));
9347         DisplayMoveError(buf1);
9348         DrawPosition(FALSE, boards[currentMove]);
9349
9350         SetUserThinkingEnables();
9351         return;
9352     }
9353     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9354         /* Program has a broken "time" command that
9355            outputs a string not ending in newline.
9356            Don't use it. */
9357         cps->sendTime = 0;
9358     }
9359     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9360         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9361             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9362     }
9363
9364     /*
9365      * If chess program startup fails, exit with an error message.
9366      * Attempts to recover here are futile. [HGM] Well, we try anyway
9367      */
9368     if ((StrStr(message, "unknown host") != NULL)
9369         || (StrStr(message, "No remote directory") != NULL)
9370         || (StrStr(message, "not found") != NULL)
9371         || (StrStr(message, "No such file") != NULL)
9372         || (StrStr(message, "can't alloc") != NULL)
9373         || (StrStr(message, "Permission denied") != NULL)) {
9374
9375         cps->maybeThinking = FALSE;
9376         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9377                 _(cps->which), cps->program, cps->host, message);
9378         RemoveInputSource(cps->isr);
9379         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9380             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9381             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9382         }
9383         return;
9384     }
9385
9386     /*
9387      * Look for hint output
9388      */
9389     if (sscanf(message, "Hint: %s", buf1) == 1) {
9390         if (cps == &first && hintRequested) {
9391             hintRequested = FALSE;
9392             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9393                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9394                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9395                                     PosFlags(forwardMostMove),
9396                                     fromY, fromX, toY, toX, promoChar, buf1);
9397                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9398                 DisplayInformation(buf2);
9399             } else {
9400                 /* Hint move could not be parsed!? */
9401               snprintf(buf2, sizeof(buf2),
9402                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9403                         buf1, _(cps->which));
9404                 DisplayError(buf2, 0);
9405             }
9406         } else {
9407           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9408         }
9409         return;
9410     }
9411
9412     /*
9413      * Ignore other messages if game is not in progress
9414      */
9415     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9416         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9417
9418     /*
9419      * look for win, lose, draw, or draw offer
9420      */
9421     if (strncmp(message, "1-0", 3) == 0) {
9422         char *p, *q, *r = "";
9423         p = strchr(message, '{');
9424         if (p) {
9425             q = strchr(p, '}');
9426             if (q) {
9427                 *q = NULLCHAR;
9428                 r = p + 1;
9429             }
9430         }
9431         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9432         return;
9433     } else if (strncmp(message, "0-1", 3) == 0) {
9434         char *p, *q, *r = "";
9435         p = strchr(message, '{');
9436         if (p) {
9437             q = strchr(p, '}');
9438             if (q) {
9439                 *q = NULLCHAR;
9440                 r = p + 1;
9441             }
9442         }
9443         /* Kludge for Arasan 4.1 bug */
9444         if (strcmp(r, "Black resigns") == 0) {
9445             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9446             return;
9447         }
9448         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9449         return;
9450     } else if (strncmp(message, "1/2", 3) == 0) {
9451         char *p, *q, *r = "";
9452         p = strchr(message, '{');
9453         if (p) {
9454             q = strchr(p, '}');
9455             if (q) {
9456                 *q = NULLCHAR;
9457                 r = p + 1;
9458             }
9459         }
9460
9461         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9462         return;
9463
9464     } else if (strncmp(message, "White resign", 12) == 0) {
9465         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9466         return;
9467     } else if (strncmp(message, "Black resign", 12) == 0) {
9468         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9469         return;
9470     } else if (strncmp(message, "White matches", 13) == 0 ||
9471                strncmp(message, "Black matches", 13) == 0   ) {
9472         /* [HGM] ignore GNUShogi noises */
9473         return;
9474     } else if (strncmp(message, "White", 5) == 0 &&
9475                message[5] != '(' &&
9476                StrStr(message, "Black") == NULL) {
9477         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9478         return;
9479     } else if (strncmp(message, "Black", 5) == 0 &&
9480                message[5] != '(') {
9481         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9482         return;
9483     } else if (strcmp(message, "resign") == 0 ||
9484                strcmp(message, "computer resigns") == 0) {
9485         switch (gameMode) {
9486           case MachinePlaysBlack:
9487           case IcsPlayingBlack:
9488             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9489             break;
9490           case MachinePlaysWhite:
9491           case IcsPlayingWhite:
9492             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9493             break;
9494           case TwoMachinesPlay:
9495             if (cps->twoMachinesColor[0] == 'w')
9496               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9497             else
9498               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9499             break;
9500           default:
9501             /* can't happen */
9502             break;
9503         }
9504         return;
9505     } else if (strncmp(message, "opponent mates", 14) == 0) {
9506         switch (gameMode) {
9507           case MachinePlaysBlack:
9508           case IcsPlayingBlack:
9509             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9510             break;
9511           case MachinePlaysWhite:
9512           case IcsPlayingWhite:
9513             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9514             break;
9515           case TwoMachinesPlay:
9516             if (cps->twoMachinesColor[0] == 'w')
9517               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9518             else
9519               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9520             break;
9521           default:
9522             /* can't happen */
9523             break;
9524         }
9525         return;
9526     } else if (strncmp(message, "computer mates", 14) == 0) {
9527         switch (gameMode) {
9528           case MachinePlaysBlack:
9529           case IcsPlayingBlack:
9530             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9531             break;
9532           case MachinePlaysWhite:
9533           case IcsPlayingWhite:
9534             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9535             break;
9536           case TwoMachinesPlay:
9537             if (cps->twoMachinesColor[0] == 'w')
9538               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9539             else
9540               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9541             break;
9542           default:
9543             /* can't happen */
9544             break;
9545         }
9546         return;
9547     } else if (strncmp(message, "checkmate", 9) == 0) {
9548         if (WhiteOnMove(forwardMostMove)) {
9549             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9550         } else {
9551             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9552         }
9553         return;
9554     } else if (strstr(message, "Draw") != NULL ||
9555                strstr(message, "game is a draw") != NULL) {
9556         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9557         return;
9558     } else if (strstr(message, "offer") != NULL &&
9559                strstr(message, "draw") != NULL) {
9560 #if ZIPPY
9561         if (appData.zippyPlay && first.initDone) {
9562             /* Relay offer to ICS */
9563             SendToICS(ics_prefix);
9564             SendToICS("draw\n");
9565         }
9566 #endif
9567         cps->offeredDraw = 2; /* valid until this engine moves twice */
9568         if (gameMode == TwoMachinesPlay) {
9569             if (cps->other->offeredDraw) {
9570                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9571             /* [HGM] in two-machine mode we delay relaying draw offer      */
9572             /* until after we also have move, to see if it is really claim */
9573             }
9574         } else if (gameMode == MachinePlaysWhite ||
9575                    gameMode == MachinePlaysBlack) {
9576           if (userOfferedDraw) {
9577             DisplayInformation(_("Machine accepts your draw offer"));
9578             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9579           } else {
9580             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9581           }
9582         }
9583     }
9584
9585
9586     /*
9587      * Look for thinking output
9588      */
9589     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9590           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9591                                 ) {
9592         int plylev, mvleft, mvtot, curscore, time;
9593         char mvname[MOVE_LEN];
9594         u64 nodes; // [DM]
9595         char plyext;
9596         int ignore = FALSE;
9597         int prefixHint = FALSE;
9598         mvname[0] = NULLCHAR;
9599
9600         switch (gameMode) {
9601           case MachinePlaysBlack:
9602           case IcsPlayingBlack:
9603             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9604             break;
9605           case MachinePlaysWhite:
9606           case IcsPlayingWhite:
9607             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9608             break;
9609           case AnalyzeMode:
9610           case AnalyzeFile:
9611             break;
9612           case IcsObserving: /* [DM] icsEngineAnalyze */
9613             if (!appData.icsEngineAnalyze) ignore = TRUE;
9614             break;
9615           case TwoMachinesPlay:
9616             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9617                 ignore = TRUE;
9618             }
9619             break;
9620           default:
9621             ignore = TRUE;
9622             break;
9623         }
9624
9625         if (!ignore) {
9626             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9627             buf1[0] = NULLCHAR;
9628             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9629                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9630                 char score_buf[MSG_SIZ];
9631
9632                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9633                     nodes += u64Const(0x100000000);
9634
9635                 if (plyext != ' ' && plyext != '\t') {
9636                     time *= 100;
9637                 }
9638
9639                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9640                 if( cps->scoreIsAbsolute &&
9641                     ( gameMode == MachinePlaysBlack ||
9642                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9643                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9644                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9645                      !WhiteOnMove(currentMove)
9646                     ) )
9647                 {
9648                     curscore = -curscore;
9649                 }
9650
9651                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9652
9653                 if(*bestMove) { // rememer time best EPD move was first found
9654                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9655                     ChessMove mt;
9656                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9657                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9658                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9659                 }
9660
9661                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9662                         char buf[MSG_SIZ];
9663                         FILE *f;
9664                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9665                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9666                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9667                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9668                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9669                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9670                                 fclose(f);
9671                         }
9672                         else
9673                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9674                           DisplayError(_("failed writing PV"), 0);
9675                 }
9676
9677                 tempStats.depth = plylev;
9678                 tempStats.nodes = nodes;
9679                 tempStats.time = time;
9680                 tempStats.score = curscore;
9681                 tempStats.got_only_move = 0;
9682
9683                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9684                         int ticklen;
9685
9686                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9687                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9688                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9689                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9690                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9691                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9692                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9693                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9694                 }
9695
9696                 /* Buffer overflow protection */
9697                 if (pv[0] != NULLCHAR) {
9698                     if (strlen(pv) >= sizeof(tempStats.movelist)
9699                         && appData.debugMode) {
9700                         fprintf(debugFP,
9701                                 "PV is too long; using the first %u bytes.\n",
9702                                 (unsigned) sizeof(tempStats.movelist) - 1);
9703                     }
9704
9705                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9706                 } else {
9707                     sprintf(tempStats.movelist, " no PV\n");
9708                 }
9709
9710                 if (tempStats.seen_stat) {
9711                     tempStats.ok_to_send = 1;
9712                 }
9713
9714                 if (strchr(tempStats.movelist, '(') != NULL) {
9715                     tempStats.line_is_book = 1;
9716                     tempStats.nr_moves = 0;
9717                     tempStats.moves_left = 0;
9718                 } else {
9719                     tempStats.line_is_book = 0;
9720                 }
9721
9722                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9723                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9724
9725                 SendProgramStatsToFrontend( cps, &tempStats );
9726
9727                 /*
9728                     [AS] Protect the thinkOutput buffer from overflow... this
9729                     is only useful if buf1 hasn't overflowed first!
9730                 */
9731                 if(curscore >= MATE_SCORE) 
9732                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9733                 else if(curscore <= -MATE_SCORE) 
9734                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9735                 else
9736                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9737                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9738                          plylev,
9739                          (gameMode == TwoMachinesPlay ?
9740                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9741                          score_buf,
9742                          prefixHint ? lastHint : "",
9743                          prefixHint ? " " : "" );
9744
9745                 if( buf1[0] != NULLCHAR ) {
9746                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9747
9748                     if( strlen(pv) > max_len ) {
9749                         if( appData.debugMode) {
9750                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9751                         }
9752                         pv[max_len+1] = '\0';
9753                     }
9754
9755                     strcat( thinkOutput, pv);
9756                 }
9757
9758                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9759                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9760                     DisplayMove(currentMove - 1);
9761                 }
9762                 return;
9763
9764             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9765                 /* crafty (9.25+) says "(only move) <move>"
9766                  * if there is only 1 legal move
9767                  */
9768                 sscanf(p, "(only move) %s", buf1);
9769                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9770                 sprintf(programStats.movelist, "%s (only move)", buf1);
9771                 programStats.depth = 1;
9772                 programStats.nr_moves = 1;
9773                 programStats.moves_left = 1;
9774                 programStats.nodes = 1;
9775                 programStats.time = 1;
9776                 programStats.got_only_move = 1;
9777
9778                 /* Not really, but we also use this member to
9779                    mean "line isn't going to change" (Crafty
9780                    isn't searching, so stats won't change) */
9781                 programStats.line_is_book = 1;
9782
9783                 SendProgramStatsToFrontend( cps, &programStats );
9784
9785                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9786                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9787                     DisplayMove(currentMove - 1);
9788                 }
9789                 return;
9790             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9791                               &time, &nodes, &plylev, &mvleft,
9792                               &mvtot, mvname) >= 5) {
9793                 /* The stat01: line is from Crafty (9.29+) in response
9794                    to the "." command */
9795                 programStats.seen_stat = 1;
9796                 cps->maybeThinking = TRUE;
9797
9798                 if (programStats.got_only_move || !appData.periodicUpdates)
9799                   return;
9800
9801                 programStats.depth = plylev;
9802                 programStats.time = time;
9803                 programStats.nodes = nodes;
9804                 programStats.moves_left = mvleft;
9805                 programStats.nr_moves = mvtot;
9806                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9807                 programStats.ok_to_send = 1;
9808                 programStats.movelist[0] = '\0';
9809
9810                 SendProgramStatsToFrontend( cps, &programStats );
9811
9812                 return;
9813
9814             } else if (strncmp(message,"++",2) == 0) {
9815                 /* Crafty 9.29+ outputs this */
9816                 programStats.got_fail = 2;
9817                 return;
9818
9819             } else if (strncmp(message,"--",2) == 0) {
9820                 /* Crafty 9.29+ outputs this */
9821                 programStats.got_fail = 1;
9822                 return;
9823
9824             } else if (thinkOutput[0] != NULLCHAR &&
9825                        strncmp(message, "    ", 4) == 0) {
9826                 unsigned message_len;
9827
9828                 p = message;
9829                 while (*p && *p == ' ') p++;
9830
9831                 message_len = strlen( p );
9832
9833                 /* [AS] Avoid buffer overflow */
9834                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9835                     strcat(thinkOutput, " ");
9836                     strcat(thinkOutput, p);
9837                 }
9838
9839                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9840                     strcat(programStats.movelist, " ");
9841                     strcat(programStats.movelist, p);
9842                 }
9843
9844                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9845                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9846                     DisplayMove(currentMove - 1);
9847                 }
9848                 return;
9849             }
9850         }
9851         else {
9852             buf1[0] = NULLCHAR;
9853
9854             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9855                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9856             {
9857                 ChessProgramStats cpstats;
9858
9859                 if (plyext != ' ' && plyext != '\t') {
9860                     time *= 100;
9861                 }
9862
9863                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9864                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9865                     curscore = -curscore;
9866                 }
9867
9868                 cpstats.depth = plylev;
9869                 cpstats.nodes = nodes;
9870                 cpstats.time = time;
9871                 cpstats.score = curscore;
9872                 cpstats.got_only_move = 0;
9873                 cpstats.movelist[0] = '\0';
9874
9875                 if (buf1[0] != NULLCHAR) {
9876                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9877                 }
9878
9879                 cpstats.ok_to_send = 0;
9880                 cpstats.line_is_book = 0;
9881                 cpstats.nr_moves = 0;
9882                 cpstats.moves_left = 0;
9883
9884                 SendProgramStatsToFrontend( cps, &cpstats );
9885             }
9886         }
9887     }
9888 }
9889
9890
9891 /* Parse a game score from the character string "game", and
9892    record it as the history of the current game.  The game
9893    score is NOT assumed to start from the standard position.
9894    The display is not updated in any way.
9895    */
9896 void
9897 ParseGameHistory (char *game)
9898 {
9899     ChessMove moveType;
9900     int fromX, fromY, toX, toY, boardIndex;
9901     char promoChar;
9902     char *p, *q;
9903     char buf[MSG_SIZ];
9904
9905     if (appData.debugMode)
9906       fprintf(debugFP, "Parsing game history: %s\n", game);
9907
9908     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9909     gameInfo.site = StrSave(appData.icsHost);
9910     gameInfo.date = PGNDate();
9911     gameInfo.round = StrSave("-");
9912
9913     /* Parse out names of players */
9914     while (*game == ' ') game++;
9915     p = buf;
9916     while (*game != ' ') *p++ = *game++;
9917     *p = NULLCHAR;
9918     gameInfo.white = StrSave(buf);
9919     while (*game == ' ') game++;
9920     p = buf;
9921     while (*game != ' ' && *game != '\n') *p++ = *game++;
9922     *p = NULLCHAR;
9923     gameInfo.black = StrSave(buf);
9924
9925     /* Parse moves */
9926     boardIndex = blackPlaysFirst ? 1 : 0;
9927     yynewstr(game);
9928     for (;;) {
9929         yyboardindex = boardIndex;
9930         moveType = (ChessMove) Myylex();
9931         switch (moveType) {
9932           case IllegalMove:             /* maybe suicide chess, etc. */
9933   if (appData.debugMode) {
9934     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9935     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9936     setbuf(debugFP, NULL);
9937   }
9938           case WhitePromotion:
9939           case BlackPromotion:
9940           case WhiteNonPromotion:
9941           case BlackNonPromotion:
9942           case NormalMove:
9943           case FirstLeg:
9944           case WhiteCapturesEnPassant:
9945           case BlackCapturesEnPassant:
9946           case WhiteKingSideCastle:
9947           case WhiteQueenSideCastle:
9948           case BlackKingSideCastle:
9949           case BlackQueenSideCastle:
9950           case WhiteKingSideCastleWild:
9951           case WhiteQueenSideCastleWild:
9952           case BlackKingSideCastleWild:
9953           case BlackQueenSideCastleWild:
9954           /* PUSH Fabien */
9955           case WhiteHSideCastleFR:
9956           case WhiteASideCastleFR:
9957           case BlackHSideCastleFR:
9958           case BlackASideCastleFR:
9959           /* POP Fabien */
9960             fromX = currentMoveString[0] - AAA;
9961             fromY = currentMoveString[1] - ONE;
9962             toX = currentMoveString[2] - AAA;
9963             toY = currentMoveString[3] - ONE;
9964             promoChar = currentMoveString[4];
9965             break;
9966           case WhiteDrop:
9967           case BlackDrop:
9968             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9969             fromX = moveType == WhiteDrop ?
9970               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9971             (int) CharToPiece(ToLower(currentMoveString[0]));
9972             fromY = DROP_RANK;
9973             toX = currentMoveString[2] - AAA;
9974             toY = currentMoveString[3] - ONE;
9975             promoChar = NULLCHAR;
9976             break;
9977           case AmbiguousMove:
9978             /* bug? */
9979             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9980   if (appData.debugMode) {
9981     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9982     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9983     setbuf(debugFP, NULL);
9984   }
9985             DisplayError(buf, 0);
9986             return;
9987           case ImpossibleMove:
9988             /* bug? */
9989             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9990   if (appData.debugMode) {
9991     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9992     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9993     setbuf(debugFP, NULL);
9994   }
9995             DisplayError(buf, 0);
9996             return;
9997           case EndOfFile:
9998             if (boardIndex < backwardMostMove) {
9999                 /* Oops, gap.  How did that happen? */
10000                 DisplayError(_("Gap in move list"), 0);
10001                 return;
10002             }
10003             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10004             if (boardIndex > forwardMostMove) {
10005                 forwardMostMove = boardIndex;
10006             }
10007             return;
10008           case ElapsedTime:
10009             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10010                 strcat(parseList[boardIndex-1], " ");
10011                 strcat(parseList[boardIndex-1], yy_text);
10012             }
10013             continue;
10014           case Comment:
10015           case PGNTag:
10016           case NAG:
10017           default:
10018             /* ignore */
10019             continue;
10020           case WhiteWins:
10021           case BlackWins:
10022           case GameIsDrawn:
10023           case GameUnfinished:
10024             if (gameMode == IcsExamining) {
10025                 if (boardIndex < backwardMostMove) {
10026                     /* Oops, gap.  How did that happen? */
10027                     return;
10028                 }
10029                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10030                 return;
10031             }
10032             gameInfo.result = moveType;
10033             p = strchr(yy_text, '{');
10034             if (p == NULL) p = strchr(yy_text, '(');
10035             if (p == NULL) {
10036                 p = yy_text;
10037                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10038             } else {
10039                 q = strchr(p, *p == '{' ? '}' : ')');
10040                 if (q != NULL) *q = NULLCHAR;
10041                 p++;
10042             }
10043             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10044             gameInfo.resultDetails = StrSave(p);
10045             continue;
10046         }
10047         if (boardIndex >= forwardMostMove &&
10048             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10049             backwardMostMove = blackPlaysFirst ? 1 : 0;
10050             return;
10051         }
10052         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10053                                  fromY, fromX, toY, toX, promoChar,
10054                                  parseList[boardIndex]);
10055         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10056         /* currentMoveString is set as a side-effect of yylex */
10057         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10058         strcat(moveList[boardIndex], "\n");
10059         boardIndex++;
10060         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10061         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10062           case MT_NONE:
10063           case MT_STALEMATE:
10064           default:
10065             break;
10066           case MT_CHECK:
10067             if(!IS_SHOGI(gameInfo.variant))
10068                 strcat(parseList[boardIndex - 1], "+");
10069             break;
10070           case MT_CHECKMATE:
10071           case MT_STAINMATE:
10072             strcat(parseList[boardIndex - 1], "#");
10073             break;
10074         }
10075     }
10076 }
10077
10078
10079 /* Apply a move to the given board  */
10080 void
10081 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10082 {
10083   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10084   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10085
10086     /* [HGM] compute & store e.p. status and castling rights for new position */
10087     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10088
10089       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10090       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10091       board[EP_STATUS] = EP_NONE;
10092       board[EP_FILE] = board[EP_RANK] = 100;
10093
10094   if (fromY == DROP_RANK) {
10095         /* must be first */
10096         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10097             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10098             return;
10099         }
10100         piece = board[toY][toX] = (ChessSquare) fromX;
10101   } else {
10102 //      ChessSquare victim;
10103       int i;
10104
10105       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10106 //           victim = board[killY][killX],
10107            killed = board[killY][killX],
10108            board[killY][killX] = EmptySquare,
10109            board[EP_STATUS] = EP_CAPTURE;
10110            if( kill2X >= 0 && kill2Y >= 0)
10111              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10112       }
10113
10114       if( board[toY][toX] != EmptySquare ) {
10115            board[EP_STATUS] = EP_CAPTURE;
10116            if( (fromX != toX || fromY != toY) && // not igui!
10117                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10118                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10119                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10120            }
10121       }
10122
10123       pawn = board[fromY][fromX];
10124       if( pawn == WhiteLance || pawn == BlackLance ) {
10125            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10126                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10127                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10128            }
10129       }
10130       if( pawn == WhitePawn ) {
10131            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10132                board[EP_STATUS] = EP_PAWN_MOVE;
10133            if( toY-fromY>=2) {
10134                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10135                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10136                         gameInfo.variant != VariantBerolina || toX < fromX)
10137                       board[EP_STATUS] = toX | berolina;
10138                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10139                         gameInfo.variant != VariantBerolina || toX > fromX)
10140                       board[EP_STATUS] = toX;
10141            }
10142       } else
10143       if( pawn == BlackPawn ) {
10144            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10145                board[EP_STATUS] = EP_PAWN_MOVE;
10146            if( toY-fromY<= -2) {
10147                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10148                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10149                         gameInfo.variant != VariantBerolina || toX < fromX)
10150                       board[EP_STATUS] = toX | berolina;
10151                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10152                         gameInfo.variant != VariantBerolina || toX > fromX)
10153                       board[EP_STATUS] = toX;
10154            }
10155        }
10156
10157        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10158        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10159        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10160        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10161
10162        for(i=0; i<nrCastlingRights; i++) {
10163            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10164               board[CASTLING][i] == toX   && castlingRank[i] == toY
10165              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10166        }
10167
10168        if(gameInfo.variant == VariantSChess) { // update virginity
10169            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10170            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10171            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10172            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10173        }
10174
10175      if (fromX == toX && fromY == toY) return;
10176
10177      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10178      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10179      if(gameInfo.variant == VariantKnightmate)
10180          king += (int) WhiteUnicorn - (int) WhiteKing;
10181
10182     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10183        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10184         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10185         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10186         board[EP_STATUS] = EP_NONE; // capture was fake!
10187     } else
10188     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10189         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10190         board[toY][toX] = piece;
10191         board[EP_STATUS] = EP_NONE; // capture was fake!
10192     } else
10193     /* Code added by Tord: */
10194     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10195     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10196         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10197       board[EP_STATUS] = EP_NONE; // capture was fake!
10198       board[fromY][fromX] = EmptySquare;
10199       board[toY][toX] = EmptySquare;
10200       if((toX > fromX) != (piece == WhiteRook)) {
10201         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10202       } else {
10203         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10204       }
10205     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10206                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10207       board[EP_STATUS] = EP_NONE;
10208       board[fromY][fromX] = EmptySquare;
10209       board[toY][toX] = EmptySquare;
10210       if((toX > fromX) != (piece == BlackRook)) {
10211         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10212       } else {
10213         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10214       }
10215     /* End of code added by Tord */
10216
10217     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10218         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10219         board[toY][toX] = piece;
10220     } else if (board[fromY][fromX] == king
10221         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10222         && toY == fromY && toX > fromX+1) {
10223         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10224         board[fromY][toX-1] = board[fromY][rookX];
10225         board[fromY][rookX] = EmptySquare;
10226         board[fromY][fromX] = EmptySquare;
10227         board[toY][toX] = king;
10228     } else if (board[fromY][fromX] == king
10229         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10230                && toY == fromY && toX < fromX-1) {
10231         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10232         board[fromY][toX+1] = board[fromY][rookX];
10233         board[fromY][rookX] = EmptySquare;
10234         board[fromY][fromX] = EmptySquare;
10235         board[toY][toX] = king;
10236     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10237                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10238                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10239                ) {
10240         /* white pawn promotion */
10241         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10242         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10243             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10244         board[fromY][fromX] = EmptySquare;
10245     } else if ((fromY >= BOARD_HEIGHT>>1)
10246                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10247                && (toX != fromX)
10248                && gameInfo.variant != VariantXiangqi
10249                && gameInfo.variant != VariantBerolina
10250                && (pawn == WhitePawn)
10251                && (board[toY][toX] == EmptySquare)) {
10252         board[fromY][fromX] = EmptySquare;
10253         board[toY][toX] = piece;
10254         if(toY == epRank - 128 + 1)
10255             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10256         else
10257             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10258     } else if ((fromY == BOARD_HEIGHT-4)
10259                && (toX == fromX)
10260                && gameInfo.variant == VariantBerolina
10261                && (board[fromY][fromX] == WhitePawn)
10262                && (board[toY][toX] == EmptySquare)) {
10263         board[fromY][fromX] = EmptySquare;
10264         board[toY][toX] = WhitePawn;
10265         if(oldEP & EP_BEROLIN_A) {
10266                 captured = board[fromY][fromX-1];
10267                 board[fromY][fromX-1] = EmptySquare;
10268         }else{  captured = board[fromY][fromX+1];
10269                 board[fromY][fromX+1] = EmptySquare;
10270         }
10271     } else if (board[fromY][fromX] == king
10272         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10273                && toY == fromY && toX > fromX+1) {
10274         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10275         board[fromY][toX-1] = board[fromY][rookX];
10276         board[fromY][rookX] = EmptySquare;
10277         board[fromY][fromX] = EmptySquare;
10278         board[toY][toX] = king;
10279     } else if (board[fromY][fromX] == king
10280         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10281                && toY == fromY && toX < fromX-1) {
10282         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10283         board[fromY][toX+1] = board[fromY][rookX];
10284         board[fromY][rookX] = EmptySquare;
10285         board[fromY][fromX] = EmptySquare;
10286         board[toY][toX] = king;
10287     } else if (fromY == 7 && fromX == 3
10288                && board[fromY][fromX] == BlackKing
10289                && toY == 7 && toX == 5) {
10290         board[fromY][fromX] = EmptySquare;
10291         board[toY][toX] = BlackKing;
10292         board[fromY][7] = EmptySquare;
10293         board[toY][4] = BlackRook;
10294     } else if (fromY == 7 && fromX == 3
10295                && board[fromY][fromX] == BlackKing
10296                && toY == 7 && toX == 1) {
10297         board[fromY][fromX] = EmptySquare;
10298         board[toY][toX] = BlackKing;
10299         board[fromY][0] = EmptySquare;
10300         board[toY][2] = BlackRook;
10301     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10302                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10303                && toY < promoRank && promoChar
10304                ) {
10305         /* black pawn promotion */
10306         board[toY][toX] = CharToPiece(ToLower(promoChar));
10307         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10308             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10309         board[fromY][fromX] = EmptySquare;
10310     } else if ((fromY < BOARD_HEIGHT>>1)
10311                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10312                && (toX != fromX)
10313                && gameInfo.variant != VariantXiangqi
10314                && gameInfo.variant != VariantBerolina
10315                && (pawn == BlackPawn)
10316                && (board[toY][toX] == EmptySquare)) {
10317         board[fromY][fromX] = EmptySquare;
10318         board[toY][toX] = piece;
10319         if(toY == epRank - 128 - 1)
10320             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10321         else
10322             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10323     } else if ((fromY == 3)
10324                && (toX == fromX)
10325                && gameInfo.variant == VariantBerolina
10326                && (board[fromY][fromX] == BlackPawn)
10327                && (board[toY][toX] == EmptySquare)) {
10328         board[fromY][fromX] = EmptySquare;
10329         board[toY][toX] = BlackPawn;
10330         if(oldEP & EP_BEROLIN_A) {
10331                 captured = board[fromY][fromX-1];
10332                 board[fromY][fromX-1] = EmptySquare;
10333         }else{  captured = board[fromY][fromX+1];
10334                 board[fromY][fromX+1] = EmptySquare;
10335         }
10336     } else {
10337         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10338         board[fromY][fromX] = EmptySquare;
10339         board[toY][toX] = piece;
10340     }
10341   }
10342
10343     if (gameInfo.holdingsWidth != 0) {
10344
10345       /* !!A lot more code needs to be written to support holdings  */
10346       /* [HGM] OK, so I have written it. Holdings are stored in the */
10347       /* penultimate board files, so they are automaticlly stored   */
10348       /* in the game history.                                       */
10349       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10350                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10351         /* Delete from holdings, by decreasing count */
10352         /* and erasing image if necessary            */
10353         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10354         if(p < (int) BlackPawn) { /* white drop */
10355              p -= (int)WhitePawn;
10356                  p = PieceToNumber((ChessSquare)p);
10357              if(p >= gameInfo.holdingsSize) p = 0;
10358              if(--board[p][BOARD_WIDTH-2] <= 0)
10359                   board[p][BOARD_WIDTH-1] = EmptySquare;
10360              if((int)board[p][BOARD_WIDTH-2] < 0)
10361                         board[p][BOARD_WIDTH-2] = 0;
10362         } else {                  /* black drop */
10363              p -= (int)BlackPawn;
10364                  p = PieceToNumber((ChessSquare)p);
10365              if(p >= gameInfo.holdingsSize) p = 0;
10366              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10367                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10368              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10369                         board[BOARD_HEIGHT-1-p][1] = 0;
10370         }
10371       }
10372       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10373           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10374         /* [HGM] holdings: Add to holdings, if holdings exist */
10375         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10376                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10377                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10378         }
10379         p = (int) captured;
10380         if (p >= (int) BlackPawn) {
10381           p -= (int)BlackPawn;
10382           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10383                   /* Restore shogi-promoted piece to its original  first */
10384                   captured = (ChessSquare) (DEMOTED(captured));
10385                   p = DEMOTED(p);
10386           }
10387           p = PieceToNumber((ChessSquare)p);
10388           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10389           board[p][BOARD_WIDTH-2]++;
10390           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10391         } else {
10392           p -= (int)WhitePawn;
10393           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10394                   captured = (ChessSquare) (DEMOTED(captured));
10395                   p = DEMOTED(p);
10396           }
10397           p = PieceToNumber((ChessSquare)p);
10398           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10399           board[BOARD_HEIGHT-1-p][1]++;
10400           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10401         }
10402       }
10403     } else if (gameInfo.variant == VariantAtomic) {
10404       if (captured != EmptySquare) {
10405         int y, x;
10406         for (y = toY-1; y <= toY+1; y++) {
10407           for (x = toX-1; x <= toX+1; x++) {
10408             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10409                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10410               board[y][x] = EmptySquare;
10411             }
10412           }
10413         }
10414         board[toY][toX] = EmptySquare;
10415       }
10416     }
10417
10418     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10419         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10420     } else
10421     if(promoChar == '+') {
10422         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10423         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10424         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10425           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10426     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10427         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10428         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10429            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10430         board[toY][toX] = newPiece;
10431     }
10432     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10433                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10434         // [HGM] superchess: take promotion piece out of holdings
10435         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10436         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10437             if(!--board[k][BOARD_WIDTH-2])
10438                 board[k][BOARD_WIDTH-1] = EmptySquare;
10439         } else {
10440             if(!--board[BOARD_HEIGHT-1-k][1])
10441                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10442         }
10443     }
10444 }
10445
10446 /* Updates forwardMostMove */
10447 void
10448 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10449 {
10450     int x = toX, y = toY;
10451     char *s = parseList[forwardMostMove];
10452     ChessSquare p = boards[forwardMostMove][toY][toX];
10453 //    forwardMostMove++; // [HGM] bare: moved downstream
10454
10455     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10456     (void) CoordsToAlgebraic(boards[forwardMostMove],
10457                              PosFlags(forwardMostMove),
10458                              fromY, fromX, y, x, promoChar,
10459                              s);
10460     if(killX >= 0 && killY >= 0)
10461         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10462
10463     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10464         int timeLeft; static int lastLoadFlag=0; int king, piece;
10465         piece = boards[forwardMostMove][fromY][fromX];
10466         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10467         if(gameInfo.variant == VariantKnightmate)
10468             king += (int) WhiteUnicorn - (int) WhiteKing;
10469         if(forwardMostMove == 0) {
10470             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10471                 fprintf(serverMoves, "%s;", UserName());
10472             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10473                 fprintf(serverMoves, "%s;", second.tidy);
10474             fprintf(serverMoves, "%s;", first.tidy);
10475             if(gameMode == MachinePlaysWhite)
10476                 fprintf(serverMoves, "%s;", UserName());
10477             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10478                 fprintf(serverMoves, "%s;", second.tidy);
10479         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10480         lastLoadFlag = loadFlag;
10481         // print base move
10482         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10483         // print castling suffix
10484         if( toY == fromY && piece == king ) {
10485             if(toX-fromX > 1)
10486                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10487             if(fromX-toX >1)
10488                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10489         }
10490         // e.p. suffix
10491         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10492              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10493              boards[forwardMostMove][toY][toX] == EmptySquare
10494              && fromX != toX && fromY != toY)
10495                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10496         // promotion suffix
10497         if(promoChar != NULLCHAR) {
10498             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10499                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10500                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10501             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10502         }
10503         if(!loadFlag) {
10504                 char buf[MOVE_LEN*2], *p; int len;
10505             fprintf(serverMoves, "/%d/%d",
10506                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10507             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10508             else                      timeLeft = blackTimeRemaining/1000;
10509             fprintf(serverMoves, "/%d", timeLeft);
10510                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10511                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10512                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10513                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10514             fprintf(serverMoves, "/%s", buf);
10515         }
10516         fflush(serverMoves);
10517     }
10518
10519     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10520         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10521       return;
10522     }
10523     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10524     if (commentList[forwardMostMove+1] != NULL) {
10525         free(commentList[forwardMostMove+1]);
10526         commentList[forwardMostMove+1] = NULL;
10527     }
10528     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10529     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10530     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10531     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10532     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10533     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10534     adjustedClock = FALSE;
10535     gameInfo.result = GameUnfinished;
10536     if (gameInfo.resultDetails != NULL) {
10537         free(gameInfo.resultDetails);
10538         gameInfo.resultDetails = NULL;
10539     }
10540     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10541                               moveList[forwardMostMove - 1]);
10542     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10543       case MT_NONE:
10544       case MT_STALEMATE:
10545       default:
10546         break;
10547       case MT_CHECK:
10548         if(!IS_SHOGI(gameInfo.variant))
10549             strcat(parseList[forwardMostMove - 1], "+");
10550         break;
10551       case MT_CHECKMATE:
10552       case MT_STAINMATE:
10553         strcat(parseList[forwardMostMove - 1], "#");
10554         break;
10555     }
10556 }
10557
10558 /* Updates currentMove if not pausing */
10559 void
10560 ShowMove (int fromX, int fromY, int toX, int toY)
10561 {
10562     int instant = (gameMode == PlayFromGameFile) ?
10563         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10564     if(appData.noGUI) return;
10565     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10566         if (!instant) {
10567             if (forwardMostMove == currentMove + 1) {
10568                 AnimateMove(boards[forwardMostMove - 1],
10569                             fromX, fromY, toX, toY);
10570             }
10571         }
10572         currentMove = forwardMostMove;
10573     }
10574
10575     killX = killY = -1; // [HGM] lion: used up
10576
10577     if (instant) return;
10578
10579     DisplayMove(currentMove - 1);
10580     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10581             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10582                 SetHighlights(fromX, fromY, toX, toY);
10583             }
10584     }
10585     DrawPosition(FALSE, boards[currentMove]);
10586     DisplayBothClocks();
10587     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10588 }
10589
10590 void
10591 SendEgtPath (ChessProgramState *cps)
10592 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10593         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10594
10595         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10596
10597         while(*p) {
10598             char c, *q = name+1, *r, *s;
10599
10600             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10601             while(*p && *p != ',') *q++ = *p++;
10602             *q++ = ':'; *q = 0;
10603             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10604                 strcmp(name, ",nalimov:") == 0 ) {
10605                 // take nalimov path from the menu-changeable option first, if it is defined
10606               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10607                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10608             } else
10609             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10610                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10611                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10612                 s = r = StrStr(s, ":") + 1; // beginning of path info
10613                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10614                 c = *r; *r = 0;             // temporarily null-terminate path info
10615                     *--q = 0;               // strip of trailig ':' from name
10616                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10617                 *r = c;
10618                 SendToProgram(buf,cps);     // send egtbpath command for this format
10619             }
10620             if(*p == ',') p++; // read away comma to position for next format name
10621         }
10622 }
10623
10624 static int
10625 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10626 {
10627       int width = 8, height = 8, holdings = 0;             // most common sizes
10628       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10629       // correct the deviations default for each variant
10630       if( v == VariantXiangqi ) width = 9,  height = 10;
10631       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10632       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10633       if( v == VariantCapablanca || v == VariantCapaRandom ||
10634           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10635                                 width = 10;
10636       if( v == VariantCourier ) width = 12;
10637       if( v == VariantSuper )                            holdings = 8;
10638       if( v == VariantGreat )   width = 10,              holdings = 8;
10639       if( v == VariantSChess )                           holdings = 7;
10640       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10641       if( v == VariantChuChess) width = 10, height = 10;
10642       if( v == VariantChu )     width = 12, height = 12;
10643       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10644              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10645              holdingsSize >= 0 && holdingsSize != holdings;
10646 }
10647
10648 char variantError[MSG_SIZ];
10649
10650 char *
10651 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10652 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10653       char *p, *variant = VariantName(v);
10654       static char b[MSG_SIZ];
10655       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10656            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10657                                                holdingsSize, variant); // cook up sized variant name
10658            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10659            if(StrStr(list, b) == NULL) {
10660                // specific sized variant not known, check if general sizing allowed
10661                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10662                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10663                             boardWidth, boardHeight, holdingsSize, engine);
10664                    return NULL;
10665                }
10666                /* [HGM] here we really should compare with the maximum supported board size */
10667            }
10668       } else snprintf(b, MSG_SIZ,"%s", variant);
10669       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10670       p = StrStr(list, b);
10671       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10672       if(p == NULL) {
10673           // occurs not at all in list, or only as sub-string
10674           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10675           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10676               int l = strlen(variantError);
10677               char *q;
10678               while(p != list && p[-1] != ',') p--;
10679               q = strchr(p, ',');
10680               if(q) *q = NULLCHAR;
10681               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10682               if(q) *q= ',';
10683           }
10684           return NULL;
10685       }
10686       return b;
10687 }
10688
10689 void
10690 InitChessProgram (ChessProgramState *cps, int setup)
10691 /* setup needed to setup FRC opening position */
10692 {
10693     char buf[MSG_SIZ], *b;
10694     if (appData.noChessProgram) return;
10695     hintRequested = FALSE;
10696     bookRequested = FALSE;
10697
10698     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10699     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10700     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10701     if(cps->memSize) { /* [HGM] memory */
10702       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10703         SendToProgram(buf, cps);
10704     }
10705     SendEgtPath(cps); /* [HGM] EGT */
10706     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10707       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10708         SendToProgram(buf, cps);
10709     }
10710
10711     setboardSpoiledMachineBlack = FALSE;
10712     SendToProgram(cps->initString, cps);
10713     if (gameInfo.variant != VariantNormal &&
10714         gameInfo.variant != VariantLoadable
10715         /* [HGM] also send variant if board size non-standard */
10716         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10717
10718       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10719                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10720       if (b == NULL) {
10721         VariantClass v;
10722         char c, *q = cps->variants, *p = strchr(q, ',');
10723         if(p) *p = NULLCHAR;
10724         v = StringToVariant(q);
10725         DisplayError(variantError, 0);
10726         if(v != VariantUnknown && cps == &first) {
10727             int w, h, s;
10728             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10729                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10730             ASSIGN(appData.variant, q);
10731             Reset(TRUE, FALSE);
10732         }
10733         if(p) *p = ',';
10734         return;
10735       }
10736
10737       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10738       SendToProgram(buf, cps);
10739     }
10740     currentlyInitializedVariant = gameInfo.variant;
10741
10742     /* [HGM] send opening position in FRC to first engine */
10743     if(setup) {
10744           SendToProgram("force\n", cps);
10745           SendBoard(cps, 0);
10746           /* engine is now in force mode! Set flag to wake it up after first move. */
10747           setboardSpoiledMachineBlack = 1;
10748     }
10749
10750     if (cps->sendICS) {
10751       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10752       SendToProgram(buf, cps);
10753     }
10754     cps->maybeThinking = FALSE;
10755     cps->offeredDraw = 0;
10756     if (!appData.icsActive) {
10757         SendTimeControl(cps, movesPerSession, timeControl,
10758                         timeIncrement, appData.searchDepth,
10759                         searchTime);
10760     }
10761     if (appData.showThinking
10762         // [HGM] thinking: four options require thinking output to be sent
10763         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10764                                 ) {
10765         SendToProgram("post\n", cps);
10766     }
10767     SendToProgram("hard\n", cps);
10768     if (!appData.ponderNextMove) {
10769         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10770            it without being sure what state we are in first.  "hard"
10771            is not a toggle, so that one is OK.
10772          */
10773         SendToProgram("easy\n", cps);
10774     }
10775     if (cps->usePing) {
10776       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10777       SendToProgram(buf, cps);
10778     }
10779     cps->initDone = TRUE;
10780     ClearEngineOutputPane(cps == &second);
10781 }
10782
10783
10784 void
10785 ResendOptions (ChessProgramState *cps)
10786 { // send the stored value of the options
10787   int i;
10788   char buf[MSG_SIZ];
10789   Option *opt = cps->option;
10790   for(i=0; i<cps->nrOptions; i++, opt++) {
10791       switch(opt->type) {
10792         case Spin:
10793         case Slider:
10794         case CheckBox:
10795             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10796           break;
10797         case ComboBox:
10798           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10799           break;
10800         default:
10801             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10802           break;
10803         case Button:
10804         case SaveButton:
10805           continue;
10806       }
10807       SendToProgram(buf, cps);
10808   }
10809 }
10810
10811 void
10812 StartChessProgram (ChessProgramState *cps)
10813 {
10814     char buf[MSG_SIZ];
10815     int err;
10816
10817     if (appData.noChessProgram) return;
10818     cps->initDone = FALSE;
10819
10820     if (strcmp(cps->host, "localhost") == 0) {
10821         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10822     } else if (*appData.remoteShell == NULLCHAR) {
10823         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10824     } else {
10825         if (*appData.remoteUser == NULLCHAR) {
10826           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10827                     cps->program);
10828         } else {
10829           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10830                     cps->host, appData.remoteUser, cps->program);
10831         }
10832         err = StartChildProcess(buf, "", &cps->pr);
10833     }
10834
10835     if (err != 0) {
10836       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10837         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10838         if(cps != &first) return;
10839         appData.noChessProgram = TRUE;
10840         ThawUI();
10841         SetNCPMode();
10842 //      DisplayFatalError(buf, err, 1);
10843 //      cps->pr = NoProc;
10844 //      cps->isr = NULL;
10845         return;
10846     }
10847
10848     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10849     if (cps->protocolVersion > 1) {
10850       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10851       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10852         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10853         cps->comboCnt = 0;  //                and values of combo boxes
10854       }
10855       SendToProgram(buf, cps);
10856       if(cps->reload) ResendOptions(cps);
10857     } else {
10858       SendToProgram("xboard\n", cps);
10859     }
10860 }
10861
10862 void
10863 TwoMachinesEventIfReady P((void))
10864 {
10865   static int curMess = 0;
10866   if (first.lastPing != first.lastPong) {
10867     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10868     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10869     return;
10870   }
10871   if (second.lastPing != second.lastPong) {
10872     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10873     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10874     return;
10875   }
10876   DisplayMessage("", ""); curMess = 0;
10877   TwoMachinesEvent();
10878 }
10879
10880 char *
10881 MakeName (char *template)
10882 {
10883     time_t clock;
10884     struct tm *tm;
10885     static char buf[MSG_SIZ];
10886     char *p = buf;
10887     int i;
10888
10889     clock = time((time_t *)NULL);
10890     tm = localtime(&clock);
10891
10892     while(*p++ = *template++) if(p[-1] == '%') {
10893         switch(*template++) {
10894           case 0:   *p = 0; return buf;
10895           case 'Y': i = tm->tm_year+1900; break;
10896           case 'y': i = tm->tm_year-100; break;
10897           case 'M': i = tm->tm_mon+1; break;
10898           case 'd': i = tm->tm_mday; break;
10899           case 'h': i = tm->tm_hour; break;
10900           case 'm': i = tm->tm_min; break;
10901           case 's': i = tm->tm_sec; break;
10902           default:  i = 0;
10903         }
10904         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10905     }
10906     return buf;
10907 }
10908
10909 int
10910 CountPlayers (char *p)
10911 {
10912     int n = 0;
10913     while(p = strchr(p, '\n')) p++, n++; // count participants
10914     return n;
10915 }
10916
10917 FILE *
10918 WriteTourneyFile (char *results, FILE *f)
10919 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10920     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10921     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10922         // create a file with tournament description
10923         fprintf(f, "-participants {%s}\n", appData.participants);
10924         fprintf(f, "-seedBase %d\n", appData.seedBase);
10925         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10926         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10927         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10928         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10929         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10930         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10931         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10932         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10933         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10934         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10935         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10936         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10937         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10938         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10939         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10940         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10941         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10942         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10943         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10944         fprintf(f, "-smpCores %d\n", appData.smpCores);
10945         if(searchTime > 0)
10946                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10947         else {
10948                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10949                 fprintf(f, "-tc %s\n", appData.timeControl);
10950                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10951         }
10952         fprintf(f, "-results \"%s\"\n", results);
10953     }
10954     return f;
10955 }
10956
10957 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10958
10959 void
10960 Substitute (char *participants, int expunge)
10961 {
10962     int i, changed, changes=0, nPlayers=0;
10963     char *p, *q, *r, buf[MSG_SIZ];
10964     if(participants == NULL) return;
10965     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10966     r = p = participants; q = appData.participants;
10967     while(*p && *p == *q) {
10968         if(*p == '\n') r = p+1, nPlayers++;
10969         p++; q++;
10970     }
10971     if(*p) { // difference
10972         while(*p && *p++ != '\n');
10973         while(*q && *q++ != '\n');
10974       changed = nPlayers;
10975         changes = 1 + (strcmp(p, q) != 0);
10976     }
10977     if(changes == 1) { // a single engine mnemonic was changed
10978         q = r; while(*q) nPlayers += (*q++ == '\n');
10979         p = buf; while(*r && (*p = *r++) != '\n') p++;
10980         *p = NULLCHAR;
10981         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10982         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10983         if(mnemonic[i]) { // The substitute is valid
10984             FILE *f;
10985             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10986                 flock(fileno(f), LOCK_EX);
10987                 ParseArgsFromFile(f);
10988                 fseek(f, 0, SEEK_SET);
10989                 FREE(appData.participants); appData.participants = participants;
10990                 if(expunge) { // erase results of replaced engine
10991                     int len = strlen(appData.results), w, b, dummy;
10992                     for(i=0; i<len; i++) {
10993                         Pairing(i, nPlayers, &w, &b, &dummy);
10994                         if((w == changed || b == changed) && appData.results[i] == '*') {
10995                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10996                             fclose(f);
10997                             return;
10998                         }
10999                     }
11000                     for(i=0; i<len; i++) {
11001                         Pairing(i, nPlayers, &w, &b, &dummy);
11002                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11003                     }
11004                 }
11005                 WriteTourneyFile(appData.results, f);
11006                 fclose(f); // release lock
11007                 return;
11008             }
11009         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11010     }
11011     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11012     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11013     free(participants);
11014     return;
11015 }
11016
11017 int
11018 CheckPlayers (char *participants)
11019 {
11020         int i;
11021         char buf[MSG_SIZ], *p;
11022         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11023         while(p = strchr(participants, '\n')) {
11024             *p = NULLCHAR;
11025             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11026             if(!mnemonic[i]) {
11027                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11028                 *p = '\n';
11029                 DisplayError(buf, 0);
11030                 return 1;
11031             }
11032             *p = '\n';
11033             participants = p + 1;
11034         }
11035         return 0;
11036 }
11037
11038 int
11039 CreateTourney (char *name)
11040 {
11041         FILE *f;
11042         if(matchMode && strcmp(name, appData.tourneyFile)) {
11043              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11044         }
11045         if(name[0] == NULLCHAR) {
11046             if(appData.participants[0])
11047                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11048             return 0;
11049         }
11050         f = fopen(name, "r");
11051         if(f) { // file exists
11052             ASSIGN(appData.tourneyFile, name);
11053             ParseArgsFromFile(f); // parse it
11054         } else {
11055             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11056             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11057                 DisplayError(_("Not enough participants"), 0);
11058                 return 0;
11059             }
11060             if(CheckPlayers(appData.participants)) return 0;
11061             ASSIGN(appData.tourneyFile, name);
11062             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11063             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11064         }
11065         fclose(f);
11066         appData.noChessProgram = FALSE;
11067         appData.clockMode = TRUE;
11068         SetGNUMode();
11069         return 1;
11070 }
11071
11072 int
11073 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11074 {
11075     char buf[MSG_SIZ], *p, *q;
11076     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11077     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11078     skip = !all && group[0]; // if group requested, we start in skip mode
11079     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11080         p = names; q = buf; header = 0;
11081         while(*p && *p != '\n') *q++ = *p++;
11082         *q = 0;
11083         if(*p == '\n') p++;
11084         if(buf[0] == '#') {
11085             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11086             depth++; // we must be entering a new group
11087             if(all) continue; // suppress printing group headers when complete list requested
11088             header = 1;
11089             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11090         }
11091         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11092         if(engineList[i]) free(engineList[i]);
11093         engineList[i] = strdup(buf);
11094         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11095         if(engineMnemonic[i]) free(engineMnemonic[i]);
11096         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11097             strcat(buf, " (");
11098             sscanf(q + 8, "%s", buf + strlen(buf));
11099             strcat(buf, ")");
11100         }
11101         engineMnemonic[i] = strdup(buf);
11102         i++;
11103     }
11104     engineList[i] = engineMnemonic[i] = NULL;
11105     return i;
11106 }
11107
11108 // following implemented as macro to avoid type limitations
11109 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11110
11111 void
11112 SwapEngines (int n)
11113 {   // swap settings for first engine and other engine (so far only some selected options)
11114     int h;
11115     char *p;
11116     if(n == 0) return;
11117     SWAP(directory, p)
11118     SWAP(chessProgram, p)
11119     SWAP(isUCI, h)
11120     SWAP(hasOwnBookUCI, h)
11121     SWAP(protocolVersion, h)
11122     SWAP(reuse, h)
11123     SWAP(scoreIsAbsolute, h)
11124     SWAP(timeOdds, h)
11125     SWAP(logo, p)
11126     SWAP(pgnName, p)
11127     SWAP(pvSAN, h)
11128     SWAP(engOptions, p)
11129     SWAP(engInitString, p)
11130     SWAP(computerString, p)
11131     SWAP(features, p)
11132     SWAP(fenOverride, p)
11133     SWAP(NPS, h)
11134     SWAP(accumulateTC, h)
11135     SWAP(drawDepth, h)
11136     SWAP(host, p)
11137     SWAP(pseudo, h)
11138 }
11139
11140 int
11141 GetEngineLine (char *s, int n)
11142 {
11143     int i;
11144     char buf[MSG_SIZ];
11145     extern char *icsNames;
11146     if(!s || !*s) return 0;
11147     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11148     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11149     if(!mnemonic[i]) return 0;
11150     if(n == 11) return 1; // just testing if there was a match
11151     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11152     if(n == 1) SwapEngines(n);
11153     ParseArgsFromString(buf);
11154     if(n == 1) SwapEngines(n);
11155     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11156         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11157         ParseArgsFromString(buf);
11158     }
11159     return 1;
11160 }
11161
11162 int
11163 SetPlayer (int player, char *p)
11164 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11165     int i;
11166     char buf[MSG_SIZ], *engineName;
11167     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11168     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11169     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11170     if(mnemonic[i]) {
11171         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11172         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11173         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11174         ParseArgsFromString(buf);
11175     } else { // no engine with this nickname is installed!
11176         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11177         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11178         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11179         ModeHighlight();
11180         DisplayError(buf, 0);
11181         return 0;
11182     }
11183     free(engineName);
11184     return i;
11185 }
11186
11187 char *recentEngines;
11188
11189 void
11190 RecentEngineEvent (int nr)
11191 {
11192     int n;
11193 //    SwapEngines(1); // bump first to second
11194 //    ReplaceEngine(&second, 1); // and load it there
11195     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11196     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11197     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11198         ReplaceEngine(&first, 0);
11199         FloatToFront(&appData.recentEngineList, command[n]);
11200     }
11201 }
11202
11203 int
11204 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11205 {   // determine players from game number
11206     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11207
11208     if(appData.tourneyType == 0) {
11209         roundsPerCycle = (nPlayers - 1) | 1;
11210         pairingsPerRound = nPlayers / 2;
11211     } else if(appData.tourneyType > 0) {
11212         roundsPerCycle = nPlayers - appData.tourneyType;
11213         pairingsPerRound = appData.tourneyType;
11214     }
11215     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11216     gamesPerCycle = gamesPerRound * roundsPerCycle;
11217     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11218     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11219     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11220     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11221     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11222     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11223
11224     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11225     if(appData.roundSync) *syncInterval = gamesPerRound;
11226
11227     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11228
11229     if(appData.tourneyType == 0) {
11230         if(curPairing == (nPlayers-1)/2 ) {
11231             *whitePlayer = curRound;
11232             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11233         } else {
11234             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11235             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11236             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11237             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11238         }
11239     } else if(appData.tourneyType > 1) {
11240         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11241         *whitePlayer = curRound + appData.tourneyType;
11242     } else if(appData.tourneyType > 0) {
11243         *whitePlayer = curPairing;
11244         *blackPlayer = curRound + appData.tourneyType;
11245     }
11246
11247     // take care of white/black alternation per round.
11248     // For cycles and games this is already taken care of by default, derived from matchGame!
11249     return curRound & 1;
11250 }
11251
11252 int
11253 NextTourneyGame (int nr, int *swapColors)
11254 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11255     char *p, *q;
11256     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11257     FILE *tf;
11258     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11259     tf = fopen(appData.tourneyFile, "r");
11260     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11261     ParseArgsFromFile(tf); fclose(tf);
11262     InitTimeControls(); // TC might be altered from tourney file
11263
11264     nPlayers = CountPlayers(appData.participants); // count participants
11265     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11266     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11267
11268     if(syncInterval) {
11269         p = q = appData.results;
11270         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11271         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11272             DisplayMessage(_("Waiting for other game(s)"),"");
11273             waitingForGame = TRUE;
11274             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11275             return 0;
11276         }
11277         waitingForGame = FALSE;
11278     }
11279
11280     if(appData.tourneyType < 0) {
11281         if(nr>=0 && !pairingReceived) {
11282             char buf[1<<16];
11283             if(pairing.pr == NoProc) {
11284                 if(!appData.pairingEngine[0]) {
11285                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11286                     return 0;
11287                 }
11288                 StartChessProgram(&pairing); // starts the pairing engine
11289             }
11290             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11291             SendToProgram(buf, &pairing);
11292             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11293             SendToProgram(buf, &pairing);
11294             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11295         }
11296         pairingReceived = 0;                              // ... so we continue here
11297         *swapColors = 0;
11298         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11299         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11300         matchGame = 1; roundNr = nr / syncInterval + 1;
11301     }
11302
11303     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11304
11305     // redefine engines, engine dir, etc.
11306     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11307     if(first.pr == NoProc) {
11308       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11309       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11310     }
11311     if(second.pr == NoProc) {
11312       SwapEngines(1);
11313       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11314       SwapEngines(1);         // and make that valid for second engine by swapping
11315       InitEngine(&second, 1);
11316     }
11317     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11318     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11319     return OK;
11320 }
11321
11322 void
11323 NextMatchGame ()
11324 {   // performs game initialization that does not invoke engines, and then tries to start the game
11325     int res, firstWhite, swapColors = 0;
11326     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11327     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
11328         char buf[MSG_SIZ];
11329         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11330         if(strcmp(buf, currentDebugFile)) { // name has changed
11331             FILE *f = fopen(buf, "w");
11332             if(f) { // if opening the new file failed, just keep using the old one
11333                 ASSIGN(currentDebugFile, buf);
11334                 fclose(debugFP);
11335                 debugFP = f;
11336             }
11337             if(appData.serverFileName) {
11338                 if(serverFP) fclose(serverFP);
11339                 serverFP = fopen(appData.serverFileName, "w");
11340                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11341                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11342             }
11343         }
11344     }
11345     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11346     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11347     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11348     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11349     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11350     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11351     Reset(FALSE, first.pr != NoProc);
11352     res = LoadGameOrPosition(matchGame); // setup game
11353     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11354     if(!res) return; // abort when bad game/pos file
11355     TwoMachinesEvent();
11356 }
11357
11358 void
11359 UserAdjudicationEvent (int result)
11360 {
11361     ChessMove gameResult = GameIsDrawn;
11362
11363     if( result > 0 ) {
11364         gameResult = WhiteWins;
11365     }
11366     else if( result < 0 ) {
11367         gameResult = BlackWins;
11368     }
11369
11370     if( gameMode == TwoMachinesPlay ) {
11371         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11372     }
11373 }
11374
11375
11376 // [HGM] save: calculate checksum of game to make games easily identifiable
11377 int
11378 StringCheckSum (char *s)
11379 {
11380         int i = 0;
11381         if(s==NULL) return 0;
11382         while(*s) i = i*259 + *s++;
11383         return i;
11384 }
11385
11386 int
11387 GameCheckSum ()
11388 {
11389         int i, sum=0;
11390         for(i=backwardMostMove; i<forwardMostMove; i++) {
11391                 sum += pvInfoList[i].depth;
11392                 sum += StringCheckSum(parseList[i]);
11393                 sum += StringCheckSum(commentList[i]);
11394                 sum *= 261;
11395         }
11396         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11397         return sum + StringCheckSum(commentList[i]);
11398 } // end of save patch
11399
11400 void
11401 GameEnds (ChessMove result, char *resultDetails, int whosays)
11402 {
11403     GameMode nextGameMode;
11404     int isIcsGame;
11405     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11406
11407     if(endingGame) return; /* [HGM] crash: forbid recursion */
11408     endingGame = 1;
11409     if(twoBoards) { // [HGM] dual: switch back to one board
11410         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11411         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11412     }
11413     if (appData.debugMode) {
11414       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11415               result, resultDetails ? resultDetails : "(null)", whosays);
11416     }
11417
11418     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11419
11420     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11421
11422     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11423         /* If we are playing on ICS, the server decides when the
11424            game is over, but the engine can offer to draw, claim
11425            a draw, or resign.
11426          */
11427 #if ZIPPY
11428         if (appData.zippyPlay && first.initDone) {
11429             if (result == GameIsDrawn) {
11430                 /* In case draw still needs to be claimed */
11431                 SendToICS(ics_prefix);
11432                 SendToICS("draw\n");
11433             } else if (StrCaseStr(resultDetails, "resign")) {
11434                 SendToICS(ics_prefix);
11435                 SendToICS("resign\n");
11436             }
11437         }
11438 #endif
11439         endingGame = 0; /* [HGM] crash */
11440         return;
11441     }
11442
11443     /* If we're loading the game from a file, stop */
11444     if (whosays == GE_FILE) {
11445       (void) StopLoadGameTimer();
11446       gameFileFP = NULL;
11447     }
11448
11449     /* Cancel draw offers */
11450     first.offeredDraw = second.offeredDraw = 0;
11451
11452     /* If this is an ICS game, only ICS can really say it's done;
11453        if not, anyone can. */
11454     isIcsGame = (gameMode == IcsPlayingWhite ||
11455                  gameMode == IcsPlayingBlack ||
11456                  gameMode == IcsObserving    ||
11457                  gameMode == IcsExamining);
11458
11459     if (!isIcsGame || whosays == GE_ICS) {
11460         /* OK -- not an ICS game, or ICS said it was done */
11461         StopClocks();
11462         if (!isIcsGame && !appData.noChessProgram)
11463           SetUserThinkingEnables();
11464
11465         /* [HGM] if a machine claims the game end we verify this claim */
11466         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11467             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11468                 char claimer;
11469                 ChessMove trueResult = (ChessMove) -1;
11470
11471                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11472                                             first.twoMachinesColor[0] :
11473                                             second.twoMachinesColor[0] ;
11474
11475                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11476                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11477                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11478                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11479                 } else
11480                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11481                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11482                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11483                 } else
11484                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11485                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11486                 }
11487
11488                 // now verify win claims, but not in drop games, as we don't understand those yet
11489                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11490                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11491                     (result == WhiteWins && claimer == 'w' ||
11492                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11493                       if (appData.debugMode) {
11494                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11495                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11496                       }
11497                       if(result != trueResult) {
11498                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11499                               result = claimer == 'w' ? BlackWins : WhiteWins;
11500                               resultDetails = buf;
11501                       }
11502                 } else
11503                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11504                     && (forwardMostMove <= backwardMostMove ||
11505                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11506                         (claimer=='b')==(forwardMostMove&1))
11507                                                                                   ) {
11508                       /* [HGM] verify: draws that were not flagged are false claims */
11509                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11510                       result = claimer == 'w' ? BlackWins : WhiteWins;
11511                       resultDetails = buf;
11512                 }
11513                 /* (Claiming a loss is accepted no questions asked!) */
11514             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11515                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11516                 result = GameUnfinished;
11517                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11518             }
11519             /* [HGM] bare: don't allow bare King to win */
11520             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11521                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11522                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11523                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11524                && result != GameIsDrawn)
11525             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11526                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11527                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11528                         if(p >= 0 && p <= (int)WhiteKing) k++;
11529                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11530                 }
11531                 if (appData.debugMode) {
11532                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11533                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11534                 }
11535                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11536                         result = GameIsDrawn;
11537                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11538                         resultDetails = buf;
11539                 }
11540             }
11541         }
11542
11543
11544         if(serverMoves != NULL && !loadFlag) { char c = '=';
11545             if(result==WhiteWins) c = '+';
11546             if(result==BlackWins) c = '-';
11547             if(resultDetails != NULL)
11548                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11549         }
11550         if (resultDetails != NULL) {
11551             gameInfo.result = result;
11552             gameInfo.resultDetails = StrSave(resultDetails);
11553
11554             /* display last move only if game was not loaded from file */
11555             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11556                 DisplayMove(currentMove - 1);
11557
11558             if (forwardMostMove != 0) {
11559                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11560                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11561                                                                 ) {
11562                     if (*appData.saveGameFile != NULLCHAR) {
11563                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11564                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11565                         else
11566                         SaveGameToFile(appData.saveGameFile, TRUE);
11567                     } else if (appData.autoSaveGames) {
11568                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11569                     }
11570                     if (*appData.savePositionFile != NULLCHAR) {
11571                         SavePositionToFile(appData.savePositionFile);
11572                     }
11573                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11574                 }
11575             }
11576
11577             /* Tell program how game ended in case it is learning */
11578             /* [HGM] Moved this to after saving the PGN, just in case */
11579             /* engine died and we got here through time loss. In that */
11580             /* case we will get a fatal error writing the pipe, which */
11581             /* would otherwise lose us the PGN.                       */
11582             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11583             /* output during GameEnds should never be fatal anymore   */
11584             if (gameMode == MachinePlaysWhite ||
11585                 gameMode == MachinePlaysBlack ||
11586                 gameMode == TwoMachinesPlay ||
11587                 gameMode == IcsPlayingWhite ||
11588                 gameMode == IcsPlayingBlack ||
11589                 gameMode == BeginningOfGame) {
11590                 char buf[MSG_SIZ];
11591                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11592                         resultDetails);
11593                 if (first.pr != NoProc) {
11594                     SendToProgram(buf, &first);
11595                 }
11596                 if (second.pr != NoProc &&
11597                     gameMode == TwoMachinesPlay) {
11598                     SendToProgram(buf, &second);
11599                 }
11600             }
11601         }
11602
11603         if (appData.icsActive) {
11604             if (appData.quietPlay &&
11605                 (gameMode == IcsPlayingWhite ||
11606                  gameMode == IcsPlayingBlack)) {
11607                 SendToICS(ics_prefix);
11608                 SendToICS("set shout 1\n");
11609             }
11610             nextGameMode = IcsIdle;
11611             ics_user_moved = FALSE;
11612             /* clean up premove.  It's ugly when the game has ended and the
11613              * premove highlights are still on the board.
11614              */
11615             if (gotPremove) {
11616               gotPremove = FALSE;
11617               ClearPremoveHighlights();
11618               DrawPosition(FALSE, boards[currentMove]);
11619             }
11620             if (whosays == GE_ICS) {
11621                 switch (result) {
11622                 case WhiteWins:
11623                     if (gameMode == IcsPlayingWhite)
11624                         PlayIcsWinSound();
11625                     else if(gameMode == IcsPlayingBlack)
11626                         PlayIcsLossSound();
11627                     break;
11628                 case BlackWins:
11629                     if (gameMode == IcsPlayingBlack)
11630                         PlayIcsWinSound();
11631                     else if(gameMode == IcsPlayingWhite)
11632                         PlayIcsLossSound();
11633                     break;
11634                 case GameIsDrawn:
11635                     PlayIcsDrawSound();
11636                     break;
11637                 default:
11638                     PlayIcsUnfinishedSound();
11639                 }
11640             }
11641             if(appData.quitNext) { ExitEvent(0); return; }
11642         } else if (gameMode == EditGame ||
11643                    gameMode == PlayFromGameFile ||
11644                    gameMode == AnalyzeMode ||
11645                    gameMode == AnalyzeFile) {
11646             nextGameMode = gameMode;
11647         } else {
11648             nextGameMode = EndOfGame;
11649         }
11650         pausing = FALSE;
11651         ModeHighlight();
11652     } else {
11653         nextGameMode = gameMode;
11654     }
11655
11656     if (appData.noChessProgram) {
11657         gameMode = nextGameMode;
11658         ModeHighlight();
11659         endingGame = 0; /* [HGM] crash */
11660         return;
11661     }
11662
11663     if (first.reuse) {
11664         /* Put first chess program into idle state */
11665         if (first.pr != NoProc &&
11666             (gameMode == MachinePlaysWhite ||
11667              gameMode == MachinePlaysBlack ||
11668              gameMode == TwoMachinesPlay ||
11669              gameMode == IcsPlayingWhite ||
11670              gameMode == IcsPlayingBlack ||
11671              gameMode == BeginningOfGame)) {
11672             SendToProgram("force\n", &first);
11673             if (first.usePing) {
11674               char buf[MSG_SIZ];
11675               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11676               SendToProgram(buf, &first);
11677             }
11678         }
11679     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11680         /* Kill off first chess program */
11681         if (first.isr != NULL)
11682           RemoveInputSource(first.isr);
11683         first.isr = NULL;
11684
11685         if (first.pr != NoProc) {
11686             ExitAnalyzeMode();
11687             DoSleep( appData.delayBeforeQuit );
11688             SendToProgram("quit\n", &first);
11689             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11690             first.reload = TRUE;
11691         }
11692         first.pr = NoProc;
11693     }
11694     if (second.reuse) {
11695         /* Put second chess program into idle state */
11696         if (second.pr != NoProc &&
11697             gameMode == TwoMachinesPlay) {
11698             SendToProgram("force\n", &second);
11699             if (second.usePing) {
11700               char buf[MSG_SIZ];
11701               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11702               SendToProgram(buf, &second);
11703             }
11704         }
11705     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11706         /* Kill off second chess program */
11707         if (second.isr != NULL)
11708           RemoveInputSource(second.isr);
11709         second.isr = NULL;
11710
11711         if (second.pr != NoProc) {
11712             DoSleep( appData.delayBeforeQuit );
11713             SendToProgram("quit\n", &second);
11714             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11715             second.reload = TRUE;
11716         }
11717         second.pr = NoProc;
11718     }
11719
11720     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11721         char resChar = '=';
11722         switch (result) {
11723         case WhiteWins:
11724           resChar = '+';
11725           if (first.twoMachinesColor[0] == 'w') {
11726             first.matchWins++;
11727           } else {
11728             second.matchWins++;
11729           }
11730           break;
11731         case BlackWins:
11732           resChar = '-';
11733           if (first.twoMachinesColor[0] == 'b') {
11734             first.matchWins++;
11735           } else {
11736             second.matchWins++;
11737           }
11738           break;
11739         case GameUnfinished:
11740           resChar = ' ';
11741         default:
11742           break;
11743         }
11744
11745         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11746         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11747             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11748             ReserveGame(nextGame, resChar); // sets nextGame
11749             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11750             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11751         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11752
11753         if (nextGame <= appData.matchGames && !abortMatch) {
11754             gameMode = nextGameMode;
11755             matchGame = nextGame; // this will be overruled in tourney mode!
11756             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11757             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11758             endingGame = 0; /* [HGM] crash */
11759             return;
11760         } else {
11761             gameMode = nextGameMode;
11762             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11763                      first.tidy, second.tidy,
11764                      first.matchWins, second.matchWins,
11765                      appData.matchGames - (first.matchWins + second.matchWins));
11766             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11767             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11768             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11769             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11770                 first.twoMachinesColor = "black\n";
11771                 second.twoMachinesColor = "white\n";
11772             } else {
11773                 first.twoMachinesColor = "white\n";
11774                 second.twoMachinesColor = "black\n";
11775             }
11776         }
11777     }
11778     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11779         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11780       ExitAnalyzeMode();
11781     gameMode = nextGameMode;
11782     ModeHighlight();
11783     endingGame = 0;  /* [HGM] crash */
11784     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11785         if(matchMode == TRUE) { // match through command line: exit with or without popup
11786             if(ranking) {
11787                 ToNrEvent(forwardMostMove);
11788                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11789                 else ExitEvent(0);
11790             } else DisplayFatalError(buf, 0, 0);
11791         } else { // match through menu; just stop, with or without popup
11792             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11793             ModeHighlight();
11794             if(ranking){
11795                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11796             } else DisplayNote(buf);
11797       }
11798       if(ranking) free(ranking);
11799     }
11800 }
11801
11802 /* Assumes program was just initialized (initString sent).
11803    Leaves program in force mode. */
11804 void
11805 FeedMovesToProgram (ChessProgramState *cps, int upto)
11806 {
11807     int i;
11808
11809     if (appData.debugMode)
11810       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11811               startedFromSetupPosition ? "position and " : "",
11812               backwardMostMove, upto, cps->which);
11813     if(currentlyInitializedVariant != gameInfo.variant) {
11814       char buf[MSG_SIZ];
11815         // [HGM] variantswitch: make engine aware of new variant
11816         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11817                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11818                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11819         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11820         SendToProgram(buf, cps);
11821         currentlyInitializedVariant = gameInfo.variant;
11822     }
11823     SendToProgram("force\n", cps);
11824     if (startedFromSetupPosition) {
11825         SendBoard(cps, backwardMostMove);
11826     if (appData.debugMode) {
11827         fprintf(debugFP, "feedMoves\n");
11828     }
11829     }
11830     for (i = backwardMostMove; i < upto; i++) {
11831         SendMoveToProgram(i, cps);
11832     }
11833 }
11834
11835
11836 int
11837 ResurrectChessProgram ()
11838 {
11839      /* The chess program may have exited.
11840         If so, restart it and feed it all the moves made so far. */
11841     static int doInit = 0;
11842
11843     if (appData.noChessProgram) return 1;
11844
11845     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11846         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11847         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11848         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11849     } else {
11850         if (first.pr != NoProc) return 1;
11851         StartChessProgram(&first);
11852     }
11853     InitChessProgram(&first, FALSE);
11854     FeedMovesToProgram(&first, currentMove);
11855
11856     if (!first.sendTime) {
11857         /* can't tell gnuchess what its clock should read,
11858            so we bow to its notion. */
11859         ResetClocks();
11860         timeRemaining[0][currentMove] = whiteTimeRemaining;
11861         timeRemaining[1][currentMove] = blackTimeRemaining;
11862     }
11863
11864     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11865                 appData.icsEngineAnalyze) && first.analysisSupport) {
11866       SendToProgram("analyze\n", &first);
11867       first.analyzing = TRUE;
11868     }
11869     return 1;
11870 }
11871
11872 /*
11873  * Button procedures
11874  */
11875 void
11876 Reset (int redraw, int init)
11877 {
11878     int i;
11879
11880     if (appData.debugMode) {
11881         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11882                 redraw, init, gameMode);
11883     }
11884     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11885     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11886     CleanupTail(); // [HGM] vari: delete any stored variations
11887     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11888     pausing = pauseExamInvalid = FALSE;
11889     startedFromSetupPosition = blackPlaysFirst = FALSE;
11890     firstMove = TRUE;
11891     whiteFlag = blackFlag = FALSE;
11892     userOfferedDraw = FALSE;
11893     hintRequested = bookRequested = FALSE;
11894     first.maybeThinking = FALSE;
11895     second.maybeThinking = FALSE;
11896     first.bookSuspend = FALSE; // [HGM] book
11897     second.bookSuspend = FALSE;
11898     thinkOutput[0] = NULLCHAR;
11899     lastHint[0] = NULLCHAR;
11900     ClearGameInfo(&gameInfo);
11901     gameInfo.variant = StringToVariant(appData.variant);
11902     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11903     ics_user_moved = ics_clock_paused = FALSE;
11904     ics_getting_history = H_FALSE;
11905     ics_gamenum = -1;
11906     white_holding[0] = black_holding[0] = NULLCHAR;
11907     ClearProgramStats();
11908     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11909
11910     ResetFrontEnd();
11911     ClearHighlights();
11912     flipView = appData.flipView;
11913     ClearPremoveHighlights();
11914     gotPremove = FALSE;
11915     alarmSounded = FALSE;
11916     killX = killY = -1; // [HGM] lion
11917
11918     GameEnds(EndOfFile, NULL, GE_PLAYER);
11919     if(appData.serverMovesName != NULL) {
11920         /* [HGM] prepare to make moves file for broadcasting */
11921         clock_t t = clock();
11922         if(serverMoves != NULL) fclose(serverMoves);
11923         serverMoves = fopen(appData.serverMovesName, "r");
11924         if(serverMoves != NULL) {
11925             fclose(serverMoves);
11926             /* delay 15 sec before overwriting, so all clients can see end */
11927             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11928         }
11929         serverMoves = fopen(appData.serverMovesName, "w");
11930     }
11931
11932     ExitAnalyzeMode();
11933     gameMode = BeginningOfGame;
11934     ModeHighlight();
11935     if(appData.icsActive) gameInfo.variant = VariantNormal;
11936     currentMove = forwardMostMove = backwardMostMove = 0;
11937     MarkTargetSquares(1);
11938     InitPosition(redraw);
11939     for (i = 0; i < MAX_MOVES; i++) {
11940         if (commentList[i] != NULL) {
11941             free(commentList[i]);
11942             commentList[i] = NULL;
11943         }
11944     }
11945     ResetClocks();
11946     timeRemaining[0][0] = whiteTimeRemaining;
11947     timeRemaining[1][0] = blackTimeRemaining;
11948
11949     if (first.pr == NoProc) {
11950         StartChessProgram(&first);
11951     }
11952     if (init) {
11953             InitChessProgram(&first, startedFromSetupPosition);
11954     }
11955     DisplayTitle("");
11956     DisplayMessage("", "");
11957     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11958     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11959     ClearMap();        // [HGM] exclude: invalidate map
11960 }
11961
11962 void
11963 AutoPlayGameLoop ()
11964 {
11965     for (;;) {
11966         if (!AutoPlayOneMove())
11967           return;
11968         if (matchMode || appData.timeDelay == 0)
11969           continue;
11970         if (appData.timeDelay < 0)
11971           return;
11972         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11973         break;
11974     }
11975 }
11976
11977 void
11978 AnalyzeNextGame()
11979 {
11980     ReloadGame(1); // next game
11981 }
11982
11983 int
11984 AutoPlayOneMove ()
11985 {
11986     int fromX, fromY, toX, toY;
11987
11988     if (appData.debugMode) {
11989       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11990     }
11991
11992     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11993       return FALSE;
11994
11995     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11996       pvInfoList[currentMove].depth = programStats.depth;
11997       pvInfoList[currentMove].score = programStats.score;
11998       pvInfoList[currentMove].time  = 0;
11999       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12000       else { // append analysis of final position as comment
12001         char buf[MSG_SIZ];
12002         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12003         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12004       }
12005       programStats.depth = 0;
12006     }
12007
12008     if (currentMove >= forwardMostMove) {
12009       if(gameMode == AnalyzeFile) {
12010           if(appData.loadGameIndex == -1) {
12011             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12012           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12013           } else {
12014           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12015         }
12016       }
12017 //      gameMode = EndOfGame;
12018 //      ModeHighlight();
12019
12020       /* [AS] Clear current move marker at the end of a game */
12021       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12022
12023       return FALSE;
12024     }
12025
12026     toX = moveList[currentMove][2] - AAA;
12027     toY = moveList[currentMove][3] - ONE;
12028
12029     if (moveList[currentMove][1] == '@') {
12030         if (appData.highlightLastMove) {
12031             SetHighlights(-1, -1, toX, toY);
12032         }
12033     } else {
12034         int viaX = moveList[currentMove][5] - AAA;
12035         int viaY = moveList[currentMove][6] - ONE;
12036         fromX = moveList[currentMove][0] - AAA;
12037         fromY = moveList[currentMove][1] - ONE;
12038
12039         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12040
12041         if(moveList[currentMove][4] == ';') { // multi-leg
12042             ChessSquare piece = boards[currentMove][viaY][viaX];
12043             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
12044             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
12045             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
12046             boards[currentMove][viaY][viaX] = piece;
12047         } else
12048         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12049
12050         if (appData.highlightLastMove) {
12051             SetHighlights(fromX, fromY, toX, toY);
12052         }
12053     }
12054     DisplayMove(currentMove);
12055     SendMoveToProgram(currentMove++, &first);
12056     DisplayBothClocks();
12057     DrawPosition(FALSE, boards[currentMove]);
12058     // [HGM] PV info: always display, routine tests if empty
12059     DisplayComment(currentMove - 1, commentList[currentMove]);
12060     return TRUE;
12061 }
12062
12063
12064 int
12065 LoadGameOneMove (ChessMove readAhead)
12066 {
12067     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12068     char promoChar = NULLCHAR;
12069     ChessMove moveType;
12070     char move[MSG_SIZ];
12071     char *p, *q;
12072
12073     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12074         gameMode != AnalyzeMode && gameMode != Training) {
12075         gameFileFP = NULL;
12076         return FALSE;
12077     }
12078
12079     yyboardindex = forwardMostMove;
12080     if (readAhead != EndOfFile) {
12081       moveType = readAhead;
12082     } else {
12083       if (gameFileFP == NULL)
12084           return FALSE;
12085       moveType = (ChessMove) Myylex();
12086     }
12087
12088     done = FALSE;
12089     switch (moveType) {
12090       case Comment:
12091         if (appData.debugMode)
12092           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12093         p = yy_text;
12094
12095         /* append the comment but don't display it */
12096         AppendComment(currentMove, p, FALSE);
12097         return TRUE;
12098
12099       case WhiteCapturesEnPassant:
12100       case BlackCapturesEnPassant:
12101       case WhitePromotion:
12102       case BlackPromotion:
12103       case WhiteNonPromotion:
12104       case BlackNonPromotion:
12105       case NormalMove:
12106       case FirstLeg:
12107       case WhiteKingSideCastle:
12108       case WhiteQueenSideCastle:
12109       case BlackKingSideCastle:
12110       case BlackQueenSideCastle:
12111       case WhiteKingSideCastleWild:
12112       case WhiteQueenSideCastleWild:
12113       case BlackKingSideCastleWild:
12114       case BlackQueenSideCastleWild:
12115       /* PUSH Fabien */
12116       case WhiteHSideCastleFR:
12117       case WhiteASideCastleFR:
12118       case BlackHSideCastleFR:
12119       case BlackASideCastleFR:
12120       /* POP Fabien */
12121         if (appData.debugMode)
12122           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12123         fromX = currentMoveString[0] - AAA;
12124         fromY = currentMoveString[1] - ONE;
12125         toX = currentMoveString[2] - AAA;
12126         toY = currentMoveString[3] - ONE;
12127         promoChar = currentMoveString[4];
12128         if(promoChar == ';') promoChar = NULLCHAR;
12129         break;
12130
12131       case WhiteDrop:
12132       case BlackDrop:
12133         if (appData.debugMode)
12134           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12135         fromX = moveType == WhiteDrop ?
12136           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12137         (int) CharToPiece(ToLower(currentMoveString[0]));
12138         fromY = DROP_RANK;
12139         toX = currentMoveString[2] - AAA;
12140         toY = currentMoveString[3] - ONE;
12141         break;
12142
12143       case WhiteWins:
12144       case BlackWins:
12145       case GameIsDrawn:
12146       case GameUnfinished:
12147         if (appData.debugMode)
12148           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12149         p = strchr(yy_text, '{');
12150         if (p == NULL) p = strchr(yy_text, '(');
12151         if (p == NULL) {
12152             p = yy_text;
12153             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12154         } else {
12155             q = strchr(p, *p == '{' ? '}' : ')');
12156             if (q != NULL) *q = NULLCHAR;
12157             p++;
12158         }
12159         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12160         GameEnds(moveType, p, GE_FILE);
12161         done = TRUE;
12162         if (cmailMsgLoaded) {
12163             ClearHighlights();
12164             flipView = WhiteOnMove(currentMove);
12165             if (moveType == GameUnfinished) flipView = !flipView;
12166             if (appData.debugMode)
12167               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12168         }
12169         break;
12170
12171       case EndOfFile:
12172         if (appData.debugMode)
12173           fprintf(debugFP, "Parser hit end of file\n");
12174         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12175           case MT_NONE:
12176           case MT_CHECK:
12177             break;
12178           case MT_CHECKMATE:
12179           case MT_STAINMATE:
12180             if (WhiteOnMove(currentMove)) {
12181                 GameEnds(BlackWins, "Black mates", GE_FILE);
12182             } else {
12183                 GameEnds(WhiteWins, "White mates", GE_FILE);
12184             }
12185             break;
12186           case MT_STALEMATE:
12187             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12188             break;
12189         }
12190         done = TRUE;
12191         break;
12192
12193       case MoveNumberOne:
12194         if (lastLoadGameStart == GNUChessGame) {
12195             /* GNUChessGames have numbers, but they aren't move numbers */
12196             if (appData.debugMode)
12197               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12198                       yy_text, (int) moveType);
12199             return LoadGameOneMove(EndOfFile); /* tail recursion */
12200         }
12201         /* else fall thru */
12202
12203       case XBoardGame:
12204       case GNUChessGame:
12205       case PGNTag:
12206         /* Reached start of next game in file */
12207         if (appData.debugMode)
12208           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12209         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12210           case MT_NONE:
12211           case MT_CHECK:
12212             break;
12213           case MT_CHECKMATE:
12214           case MT_STAINMATE:
12215             if (WhiteOnMove(currentMove)) {
12216                 GameEnds(BlackWins, "Black mates", GE_FILE);
12217             } else {
12218                 GameEnds(WhiteWins, "White mates", GE_FILE);
12219             }
12220             break;
12221           case MT_STALEMATE:
12222             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12223             break;
12224         }
12225         done = TRUE;
12226         break;
12227
12228       case PositionDiagram:     /* should not happen; ignore */
12229       case ElapsedTime:         /* ignore */
12230       case NAG:                 /* ignore */
12231         if (appData.debugMode)
12232           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12233                   yy_text, (int) moveType);
12234         return LoadGameOneMove(EndOfFile); /* tail recursion */
12235
12236       case IllegalMove:
12237         if (appData.testLegality) {
12238             if (appData.debugMode)
12239               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12240             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12241                     (forwardMostMove / 2) + 1,
12242                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12243             DisplayError(move, 0);
12244             done = TRUE;
12245         } else {
12246             if (appData.debugMode)
12247               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12248                       yy_text, currentMoveString);
12249             if(currentMoveString[1] == '@') {
12250                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12251                 fromY = DROP_RANK;
12252             } else {
12253                 fromX = currentMoveString[0] - AAA;
12254                 fromY = currentMoveString[1] - ONE;
12255             }
12256             toX = currentMoveString[2] - AAA;
12257             toY = currentMoveString[3] - ONE;
12258             promoChar = currentMoveString[4];
12259         }
12260         break;
12261
12262       case AmbiguousMove:
12263         if (appData.debugMode)
12264           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12265         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12266                 (forwardMostMove / 2) + 1,
12267                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12268         DisplayError(move, 0);
12269         done = TRUE;
12270         break;
12271
12272       default:
12273       case ImpossibleMove:
12274         if (appData.debugMode)
12275           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12276         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12277                 (forwardMostMove / 2) + 1,
12278                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12279         DisplayError(move, 0);
12280         done = TRUE;
12281         break;
12282     }
12283
12284     if (done) {
12285         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12286             DrawPosition(FALSE, boards[currentMove]);
12287             DisplayBothClocks();
12288             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12289               DisplayComment(currentMove - 1, commentList[currentMove]);
12290         }
12291         (void) StopLoadGameTimer();
12292         gameFileFP = NULL;
12293         cmailOldMove = forwardMostMove;
12294         return FALSE;
12295     } else {
12296         /* currentMoveString is set as a side-effect of yylex */
12297
12298         thinkOutput[0] = NULLCHAR;
12299         MakeMove(fromX, fromY, toX, toY, promoChar);
12300         killX = killY = -1; // [HGM] lion: used up
12301         currentMove = forwardMostMove;
12302         return TRUE;
12303     }
12304 }
12305
12306 /* Load the nth game from the given file */
12307 int
12308 LoadGameFromFile (char *filename, int n, char *title, int useList)
12309 {
12310     FILE *f;
12311     char buf[MSG_SIZ];
12312
12313     if (strcmp(filename, "-") == 0) {
12314         f = stdin;
12315         title = "stdin";
12316     } else {
12317         f = fopen(filename, "rb");
12318         if (f == NULL) {
12319           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12320             DisplayError(buf, errno);
12321             return FALSE;
12322         }
12323     }
12324     if (fseek(f, 0, 0) == -1) {
12325         /* f is not seekable; probably a pipe */
12326         useList = FALSE;
12327     }
12328     if (useList && n == 0) {
12329         int error = GameListBuild(f);
12330         if (error) {
12331             DisplayError(_("Cannot build game list"), error);
12332         } else if (!ListEmpty(&gameList) &&
12333                    ((ListGame *) gameList.tailPred)->number > 1) {
12334             GameListPopUp(f, title);
12335             return TRUE;
12336         }
12337         GameListDestroy();
12338         n = 1;
12339     }
12340     if (n == 0) n = 1;
12341     return LoadGame(f, n, title, FALSE);
12342 }
12343
12344
12345 void
12346 MakeRegisteredMove ()
12347 {
12348     int fromX, fromY, toX, toY;
12349     char promoChar;
12350     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12351         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12352           case CMAIL_MOVE:
12353           case CMAIL_DRAW:
12354             if (appData.debugMode)
12355               fprintf(debugFP, "Restoring %s for game %d\n",
12356                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12357
12358             thinkOutput[0] = NULLCHAR;
12359             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12360             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12361             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12362             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12363             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12364             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12365             MakeMove(fromX, fromY, toX, toY, promoChar);
12366             ShowMove(fromX, fromY, toX, toY);
12367
12368             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12369               case MT_NONE:
12370               case MT_CHECK:
12371                 break;
12372
12373               case MT_CHECKMATE:
12374               case MT_STAINMATE:
12375                 if (WhiteOnMove(currentMove)) {
12376                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12377                 } else {
12378                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12379                 }
12380                 break;
12381
12382               case MT_STALEMATE:
12383                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12384                 break;
12385             }
12386
12387             break;
12388
12389           case CMAIL_RESIGN:
12390             if (WhiteOnMove(currentMove)) {
12391                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12392             } else {
12393                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12394             }
12395             break;
12396
12397           case CMAIL_ACCEPT:
12398             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12399             break;
12400
12401           default:
12402             break;
12403         }
12404     }
12405
12406     return;
12407 }
12408
12409 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12410 int
12411 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12412 {
12413     int retVal;
12414
12415     if (gameNumber > nCmailGames) {
12416         DisplayError(_("No more games in this message"), 0);
12417         return FALSE;
12418     }
12419     if (f == lastLoadGameFP) {
12420         int offset = gameNumber - lastLoadGameNumber;
12421         if (offset == 0) {
12422             cmailMsg[0] = NULLCHAR;
12423             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12424                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12425                 nCmailMovesRegistered--;
12426             }
12427             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12428             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12429                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12430             }
12431         } else {
12432             if (! RegisterMove()) return FALSE;
12433         }
12434     }
12435
12436     retVal = LoadGame(f, gameNumber, title, useList);
12437
12438     /* Make move registered during previous look at this game, if any */
12439     MakeRegisteredMove();
12440
12441     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12442         commentList[currentMove]
12443           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12444         DisplayComment(currentMove - 1, commentList[currentMove]);
12445     }
12446
12447     return retVal;
12448 }
12449
12450 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12451 int
12452 ReloadGame (int offset)
12453 {
12454     int gameNumber = lastLoadGameNumber + offset;
12455     if (lastLoadGameFP == NULL) {
12456         DisplayError(_("No game has been loaded yet"), 0);
12457         return FALSE;
12458     }
12459     if (gameNumber <= 0) {
12460         DisplayError(_("Can't back up any further"), 0);
12461         return FALSE;
12462     }
12463     if (cmailMsgLoaded) {
12464         return CmailLoadGame(lastLoadGameFP, gameNumber,
12465                              lastLoadGameTitle, lastLoadGameUseList);
12466     } else {
12467         return LoadGame(lastLoadGameFP, gameNumber,
12468                         lastLoadGameTitle, lastLoadGameUseList);
12469     }
12470 }
12471
12472 int keys[EmptySquare+1];
12473
12474 int
12475 PositionMatches (Board b1, Board b2)
12476 {
12477     int r, f, sum=0;
12478     switch(appData.searchMode) {
12479         case 1: return CompareWithRights(b1, b2);
12480         case 2:
12481             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12482                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12483             }
12484             return TRUE;
12485         case 3:
12486             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12487               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12488                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12489             }
12490             return sum==0;
12491         case 4:
12492             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12493                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12494             }
12495             return sum==0;
12496     }
12497     return TRUE;
12498 }
12499
12500 #define Q_PROMO  4
12501 #define Q_EP     3
12502 #define Q_BCASTL 2
12503 #define Q_WCASTL 1
12504
12505 int pieceList[256], quickBoard[256];
12506 ChessSquare pieceType[256] = { EmptySquare };
12507 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12508 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12509 int soughtTotal, turn;
12510 Boolean epOK, flipSearch;
12511
12512 typedef struct {
12513     unsigned char piece, to;
12514 } Move;
12515
12516 #define DSIZE (250000)
12517
12518 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12519 Move *moveDatabase = initialSpace;
12520 unsigned int movePtr, dataSize = DSIZE;
12521
12522 int
12523 MakePieceList (Board board, int *counts)
12524 {
12525     int r, f, n=Q_PROMO, total=0;
12526     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12527     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12528         int sq = f + (r<<4);
12529         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12530             quickBoard[sq] = ++n;
12531             pieceList[n] = sq;
12532             pieceType[n] = board[r][f];
12533             counts[board[r][f]]++;
12534             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12535             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12536             total++;
12537         }
12538     }
12539     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12540     return total;
12541 }
12542
12543 void
12544 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12545 {
12546     int sq = fromX + (fromY<<4);
12547     int piece = quickBoard[sq], rook;
12548     quickBoard[sq] = 0;
12549     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12550     if(piece == pieceList[1] && fromY == toY) {
12551       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12552         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12553         moveDatabase[movePtr++].piece = Q_WCASTL;
12554         quickBoard[sq] = piece;
12555         piece = quickBoard[from]; quickBoard[from] = 0;
12556         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12557       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12558         quickBoard[sq] = 0; // remove Rook
12559         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12560         moveDatabase[movePtr++].piece = Q_WCASTL;
12561         quickBoard[sq] = pieceList[1]; // put King
12562         piece = rook;
12563         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12564       }
12565     } else
12566     if(piece == pieceList[2] && fromY == toY) {
12567       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12568         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12569         moveDatabase[movePtr++].piece = Q_BCASTL;
12570         quickBoard[sq] = piece;
12571         piece = quickBoard[from]; quickBoard[from] = 0;
12572         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12573       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12574         quickBoard[sq] = 0; // remove Rook
12575         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12576         moveDatabase[movePtr++].piece = Q_BCASTL;
12577         quickBoard[sq] = pieceList[2]; // put King
12578         piece = rook;
12579         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12580       }
12581     } else
12582     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12583         quickBoard[(fromY<<4)+toX] = 0;
12584         moveDatabase[movePtr].piece = Q_EP;
12585         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12586         moveDatabase[movePtr].to = sq;
12587     } else
12588     if(promoPiece != pieceType[piece]) {
12589         moveDatabase[movePtr++].piece = Q_PROMO;
12590         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12591     }
12592     moveDatabase[movePtr].piece = piece;
12593     quickBoard[sq] = piece;
12594     movePtr++;
12595 }
12596
12597 int
12598 PackGame (Board board)
12599 {
12600     Move *newSpace = NULL;
12601     moveDatabase[movePtr].piece = 0; // terminate previous game
12602     if(movePtr > dataSize) {
12603         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12604         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12605         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12606         if(newSpace) {
12607             int i;
12608             Move *p = moveDatabase, *q = newSpace;
12609             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12610             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12611             moveDatabase = newSpace;
12612         } else { // calloc failed, we must be out of memory. Too bad...
12613             dataSize = 0; // prevent calloc events for all subsequent games
12614             return 0;     // and signal this one isn't cached
12615         }
12616     }
12617     movePtr++;
12618     MakePieceList(board, counts);
12619     return movePtr;
12620 }
12621
12622 int
12623 QuickCompare (Board board, int *minCounts, int *maxCounts)
12624 {   // compare according to search mode
12625     int r, f;
12626     switch(appData.searchMode)
12627     {
12628       case 1: // exact position match
12629         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12630         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12631             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12632         }
12633         break;
12634       case 2: // can have extra material on empty squares
12635         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12636             if(board[r][f] == EmptySquare) continue;
12637             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12638         }
12639         break;
12640       case 3: // material with exact Pawn structure
12641         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12642             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12643             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12644         } // fall through to material comparison
12645       case 4: // exact material
12646         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12647         break;
12648       case 6: // material range with given imbalance
12649         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12650         // fall through to range comparison
12651       case 5: // material range
12652         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12653     }
12654     return TRUE;
12655 }
12656
12657 int
12658 QuickScan (Board board, Move *move)
12659 {   // reconstruct game,and compare all positions in it
12660     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12661     do {
12662         int piece = move->piece;
12663         int to = move->to, from = pieceList[piece];
12664         if(found < 0) { // if already found just scan to game end for final piece count
12665           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12666            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12667            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12668                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12669             ) {
12670             static int lastCounts[EmptySquare+1];
12671             int i;
12672             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12673             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12674           } else stretch = 0;
12675           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12676           if(found >= 0 && !appData.minPieces) return found;
12677         }
12678         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12679           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12680           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12681             piece = (++move)->piece;
12682             from = pieceList[piece];
12683             counts[pieceType[piece]]--;
12684             pieceType[piece] = (ChessSquare) move->to;
12685             counts[move->to]++;
12686           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12687             counts[pieceType[quickBoard[to]]]--;
12688             quickBoard[to] = 0; total--;
12689             move++;
12690             continue;
12691           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12692             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12693             from  = pieceList[piece]; // so this must be King
12694             quickBoard[from] = 0;
12695             pieceList[piece] = to;
12696             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12697             quickBoard[from] = 0; // rook
12698             quickBoard[to] = piece;
12699             to = move->to; piece = move->piece;
12700             goto aftercastle;
12701           }
12702         }
12703         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12704         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12705         quickBoard[from] = 0;
12706       aftercastle:
12707         quickBoard[to] = piece;
12708         pieceList[piece] = to;
12709         cnt++; turn ^= 3;
12710         move++;
12711     } while(1);
12712 }
12713
12714 void
12715 InitSearch ()
12716 {
12717     int r, f;
12718     flipSearch = FALSE;
12719     CopyBoard(soughtBoard, boards[currentMove]);
12720     soughtTotal = MakePieceList(soughtBoard, maxSought);
12721     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12722     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12723     CopyBoard(reverseBoard, boards[currentMove]);
12724     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12725         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12726         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12727         reverseBoard[r][f] = piece;
12728     }
12729     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12730     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12731     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12732                  || (boards[currentMove][CASTLING][2] == NoRights ||
12733                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12734                  && (boards[currentMove][CASTLING][5] == NoRights ||
12735                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12736       ) {
12737         flipSearch = TRUE;
12738         CopyBoard(flipBoard, soughtBoard);
12739         CopyBoard(rotateBoard, reverseBoard);
12740         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12741             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12742             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12743         }
12744     }
12745     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12746     if(appData.searchMode >= 5) {
12747         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12748         MakePieceList(soughtBoard, minSought);
12749         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12750     }
12751     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12752         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12753 }
12754
12755 GameInfo dummyInfo;
12756 static int creatingBook;
12757
12758 int
12759 GameContainsPosition (FILE *f, ListGame *lg)
12760 {
12761     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12762     int fromX, fromY, toX, toY;
12763     char promoChar;
12764     static int initDone=FALSE;
12765
12766     // weed out games based on numerical tag comparison
12767     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12768     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12769     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12770     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12771     if(!initDone) {
12772         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12773         initDone = TRUE;
12774     }
12775     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12776     else CopyBoard(boards[scratch], initialPosition); // default start position
12777     if(lg->moves) {
12778         turn = btm + 1;
12779         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12780         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12781     }
12782     if(btm) plyNr++;
12783     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12784     fseek(f, lg->offset, 0);
12785     yynewfile(f);
12786     while(1) {
12787         yyboardindex = scratch;
12788         quickFlag = plyNr+1;
12789         next = Myylex();
12790         quickFlag = 0;
12791         switch(next) {
12792             case PGNTag:
12793                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12794             default:
12795                 continue;
12796
12797             case XBoardGame:
12798             case GNUChessGame:
12799                 if(plyNr) return -1; // after we have seen moves, this is for new game
12800               continue;
12801
12802             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12803             case ImpossibleMove:
12804             case WhiteWins: // game ends here with these four
12805             case BlackWins:
12806             case GameIsDrawn:
12807             case GameUnfinished:
12808                 return -1;
12809
12810             case IllegalMove:
12811                 if(appData.testLegality) return -1;
12812             case WhiteCapturesEnPassant:
12813             case BlackCapturesEnPassant:
12814             case WhitePromotion:
12815             case BlackPromotion:
12816             case WhiteNonPromotion:
12817             case BlackNonPromotion:
12818             case NormalMove:
12819             case FirstLeg:
12820             case WhiteKingSideCastle:
12821             case WhiteQueenSideCastle:
12822             case BlackKingSideCastle:
12823             case BlackQueenSideCastle:
12824             case WhiteKingSideCastleWild:
12825             case WhiteQueenSideCastleWild:
12826             case BlackKingSideCastleWild:
12827             case BlackQueenSideCastleWild:
12828             case WhiteHSideCastleFR:
12829             case WhiteASideCastleFR:
12830             case BlackHSideCastleFR:
12831             case BlackASideCastleFR:
12832                 fromX = currentMoveString[0] - AAA;
12833                 fromY = currentMoveString[1] - ONE;
12834                 toX = currentMoveString[2] - AAA;
12835                 toY = currentMoveString[3] - ONE;
12836                 promoChar = currentMoveString[4];
12837                 break;
12838             case WhiteDrop:
12839             case BlackDrop:
12840                 fromX = next == WhiteDrop ?
12841                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12842                   (int) CharToPiece(ToLower(currentMoveString[0]));
12843                 fromY = DROP_RANK;
12844                 toX = currentMoveString[2] - AAA;
12845                 toY = currentMoveString[3] - ONE;
12846                 promoChar = 0;
12847                 break;
12848         }
12849         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12850         plyNr++;
12851         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12852         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12853         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12854         if(appData.findMirror) {
12855             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12856             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12857         }
12858     }
12859 }
12860
12861 /* Load the nth game from open file f */
12862 int
12863 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12864 {
12865     ChessMove cm;
12866     char buf[MSG_SIZ];
12867     int gn = gameNumber;
12868     ListGame *lg = NULL;
12869     int numPGNTags = 0;
12870     int err, pos = -1;
12871     GameMode oldGameMode;
12872     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12873     char oldName[MSG_SIZ];
12874
12875     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12876
12877     if (appData.debugMode)
12878         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12879
12880     if (gameMode == Training )
12881         SetTrainingModeOff();
12882
12883     oldGameMode = gameMode;
12884     if (gameMode != BeginningOfGame) {
12885       Reset(FALSE, TRUE);
12886     }
12887     killX = killY = -1; // [HGM] lion: in case we did not Reset
12888
12889     gameFileFP = f;
12890     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12891         fclose(lastLoadGameFP);
12892     }
12893
12894     if (useList) {
12895         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12896
12897         if (lg) {
12898             fseek(f, lg->offset, 0);
12899             GameListHighlight(gameNumber);
12900             pos = lg->position;
12901             gn = 1;
12902         }
12903         else {
12904             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12905               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12906             else
12907             DisplayError(_("Game number out of range"), 0);
12908             return FALSE;
12909         }
12910     } else {
12911         GameListDestroy();
12912         if (fseek(f, 0, 0) == -1) {
12913             if (f == lastLoadGameFP ?
12914                 gameNumber == lastLoadGameNumber + 1 :
12915                 gameNumber == 1) {
12916                 gn = 1;
12917             } else {
12918                 DisplayError(_("Can't seek on game file"), 0);
12919                 return FALSE;
12920             }
12921         }
12922     }
12923     lastLoadGameFP = f;
12924     lastLoadGameNumber = gameNumber;
12925     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12926     lastLoadGameUseList = useList;
12927
12928     yynewfile(f);
12929
12930     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12931       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12932                 lg->gameInfo.black);
12933             DisplayTitle(buf);
12934     } else if (*title != NULLCHAR) {
12935         if (gameNumber > 1) {
12936           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12937             DisplayTitle(buf);
12938         } else {
12939             DisplayTitle(title);
12940         }
12941     }
12942
12943     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12944         gameMode = PlayFromGameFile;
12945         ModeHighlight();
12946     }
12947
12948     currentMove = forwardMostMove = backwardMostMove = 0;
12949     CopyBoard(boards[0], initialPosition);
12950     StopClocks();
12951
12952     /*
12953      * Skip the first gn-1 games in the file.
12954      * Also skip over anything that precedes an identifiable
12955      * start of game marker, to avoid being confused by
12956      * garbage at the start of the file.  Currently
12957      * recognized start of game markers are the move number "1",
12958      * the pattern "gnuchess .* game", the pattern
12959      * "^[#;%] [^ ]* game file", and a PGN tag block.
12960      * A game that starts with one of the latter two patterns
12961      * will also have a move number 1, possibly
12962      * following a position diagram.
12963      * 5-4-02: Let's try being more lenient and allowing a game to
12964      * start with an unnumbered move.  Does that break anything?
12965      */
12966     cm = lastLoadGameStart = EndOfFile;
12967     while (gn > 0) {
12968         yyboardindex = forwardMostMove;
12969         cm = (ChessMove) Myylex();
12970         switch (cm) {
12971           case EndOfFile:
12972             if (cmailMsgLoaded) {
12973                 nCmailGames = CMAIL_MAX_GAMES - gn;
12974             } else {
12975                 Reset(TRUE, TRUE);
12976                 DisplayError(_("Game not found in file"), 0);
12977             }
12978             return FALSE;
12979
12980           case GNUChessGame:
12981           case XBoardGame:
12982             gn--;
12983             lastLoadGameStart = cm;
12984             break;
12985
12986           case MoveNumberOne:
12987             switch (lastLoadGameStart) {
12988               case GNUChessGame:
12989               case XBoardGame:
12990               case PGNTag:
12991                 break;
12992               case MoveNumberOne:
12993               case EndOfFile:
12994                 gn--;           /* count this game */
12995                 lastLoadGameStart = cm;
12996                 break;
12997               default:
12998                 /* impossible */
12999                 break;
13000             }
13001             break;
13002
13003           case PGNTag:
13004             switch (lastLoadGameStart) {
13005               case GNUChessGame:
13006               case PGNTag:
13007               case MoveNumberOne:
13008               case EndOfFile:
13009                 gn--;           /* count this game */
13010                 lastLoadGameStart = cm;
13011                 break;
13012               case XBoardGame:
13013                 lastLoadGameStart = cm; /* game counted already */
13014                 break;
13015               default:
13016                 /* impossible */
13017                 break;
13018             }
13019             if (gn > 0) {
13020                 do {
13021                     yyboardindex = forwardMostMove;
13022                     cm = (ChessMove) Myylex();
13023                 } while (cm == PGNTag || cm == Comment);
13024             }
13025             break;
13026
13027           case WhiteWins:
13028           case BlackWins:
13029           case GameIsDrawn:
13030             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13031                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13032                     != CMAIL_OLD_RESULT) {
13033                     nCmailResults ++ ;
13034                     cmailResult[  CMAIL_MAX_GAMES
13035                                 - gn - 1] = CMAIL_OLD_RESULT;
13036                 }
13037             }
13038             break;
13039
13040           case NormalMove:
13041           case FirstLeg:
13042             /* Only a NormalMove can be at the start of a game
13043              * without a position diagram. */
13044             if (lastLoadGameStart == EndOfFile ) {
13045               gn--;
13046               lastLoadGameStart = MoveNumberOne;
13047             }
13048             break;
13049
13050           default:
13051             break;
13052         }
13053     }
13054
13055     if (appData.debugMode)
13056       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13057
13058     if (cm == XBoardGame) {
13059         /* Skip any header junk before position diagram and/or move 1 */
13060         for (;;) {
13061             yyboardindex = forwardMostMove;
13062             cm = (ChessMove) Myylex();
13063
13064             if (cm == EndOfFile ||
13065                 cm == GNUChessGame || cm == XBoardGame) {
13066                 /* Empty game; pretend end-of-file and handle later */
13067                 cm = EndOfFile;
13068                 break;
13069             }
13070
13071             if (cm == MoveNumberOne || cm == PositionDiagram ||
13072                 cm == PGNTag || cm == Comment)
13073               break;
13074         }
13075     } else if (cm == GNUChessGame) {
13076         if (gameInfo.event != NULL) {
13077             free(gameInfo.event);
13078         }
13079         gameInfo.event = StrSave(yy_text);
13080     }
13081
13082     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13083     while (cm == PGNTag) {
13084         if (appData.debugMode)
13085           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13086         err = ParsePGNTag(yy_text, &gameInfo);
13087         if (!err) numPGNTags++;
13088
13089         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13090         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13091             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13092             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13093             InitPosition(TRUE);
13094             oldVariant = gameInfo.variant;
13095             if (appData.debugMode)
13096               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13097         }
13098
13099
13100         if (gameInfo.fen != NULL) {
13101           Board initial_position;
13102           startedFromSetupPosition = TRUE;
13103           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13104             Reset(TRUE, TRUE);
13105             DisplayError(_("Bad FEN position in file"), 0);
13106             return FALSE;
13107           }
13108           CopyBoard(boards[0], initial_position);
13109           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13110             CopyBoard(initialPosition, initial_position);
13111           if (blackPlaysFirst) {
13112             currentMove = forwardMostMove = backwardMostMove = 1;
13113             CopyBoard(boards[1], initial_position);
13114             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13115             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13116             timeRemaining[0][1] = whiteTimeRemaining;
13117             timeRemaining[1][1] = blackTimeRemaining;
13118             if (commentList[0] != NULL) {
13119               commentList[1] = commentList[0];
13120               commentList[0] = NULL;
13121             }
13122           } else {
13123             currentMove = forwardMostMove = backwardMostMove = 0;
13124           }
13125           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13126           {   int i;
13127               initialRulePlies = FENrulePlies;
13128               for( i=0; i< nrCastlingRights; i++ )
13129                   initialRights[i] = initial_position[CASTLING][i];
13130           }
13131           yyboardindex = forwardMostMove;
13132           free(gameInfo.fen);
13133           gameInfo.fen = NULL;
13134         }
13135
13136         yyboardindex = forwardMostMove;
13137         cm = (ChessMove) Myylex();
13138
13139         /* Handle comments interspersed among the tags */
13140         while (cm == Comment) {
13141             char *p;
13142             if (appData.debugMode)
13143               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13144             p = yy_text;
13145             AppendComment(currentMove, p, FALSE);
13146             yyboardindex = forwardMostMove;
13147             cm = (ChessMove) Myylex();
13148         }
13149     }
13150
13151     /* don't rely on existence of Event tag since if game was
13152      * pasted from clipboard the Event tag may not exist
13153      */
13154     if (numPGNTags > 0){
13155         char *tags;
13156         if (gameInfo.variant == VariantNormal) {
13157           VariantClass v = StringToVariant(gameInfo.event);
13158           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13159           if(v < VariantShogi) gameInfo.variant = v;
13160         }
13161         if (!matchMode) {
13162           if( appData.autoDisplayTags ) {
13163             tags = PGNTags(&gameInfo);
13164             TagsPopUp(tags, CmailMsg());
13165             free(tags);
13166           }
13167         }
13168     } else {
13169         /* Make something up, but don't display it now */
13170         SetGameInfo();
13171         TagsPopDown();
13172     }
13173
13174     if (cm == PositionDiagram) {
13175         int i, j;
13176         char *p;
13177         Board initial_position;
13178
13179         if (appData.debugMode)
13180           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13181
13182         if (!startedFromSetupPosition) {
13183             p = yy_text;
13184             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13185               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13186                 switch (*p) {
13187                   case '{':
13188                   case '[':
13189                   case '-':
13190                   case ' ':
13191                   case '\t':
13192                   case '\n':
13193                   case '\r':
13194                     break;
13195                   default:
13196                     initial_position[i][j++] = CharToPiece(*p);
13197                     break;
13198                 }
13199             while (*p == ' ' || *p == '\t' ||
13200                    *p == '\n' || *p == '\r') p++;
13201
13202             if (strncmp(p, "black", strlen("black"))==0)
13203               blackPlaysFirst = TRUE;
13204             else
13205               blackPlaysFirst = FALSE;
13206             startedFromSetupPosition = TRUE;
13207
13208             CopyBoard(boards[0], initial_position);
13209             if (blackPlaysFirst) {
13210                 currentMove = forwardMostMove = backwardMostMove = 1;
13211                 CopyBoard(boards[1], initial_position);
13212                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13213                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13214                 timeRemaining[0][1] = whiteTimeRemaining;
13215                 timeRemaining[1][1] = blackTimeRemaining;
13216                 if (commentList[0] != NULL) {
13217                     commentList[1] = commentList[0];
13218                     commentList[0] = NULL;
13219                 }
13220             } else {
13221                 currentMove = forwardMostMove = backwardMostMove = 0;
13222             }
13223         }
13224         yyboardindex = forwardMostMove;
13225         cm = (ChessMove) Myylex();
13226     }
13227
13228   if(!creatingBook) {
13229     if (first.pr == NoProc) {
13230         StartChessProgram(&first);
13231     }
13232     InitChessProgram(&first, FALSE);
13233     if(gameInfo.variant == VariantUnknown && *oldName) {
13234         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13235         gameInfo.variant = v;
13236     }
13237     SendToProgram("force\n", &first);
13238     if (startedFromSetupPosition) {
13239         SendBoard(&first, forwardMostMove);
13240     if (appData.debugMode) {
13241         fprintf(debugFP, "Load Game\n");
13242     }
13243         DisplayBothClocks();
13244     }
13245   }
13246
13247     /* [HGM] server: flag to write setup moves in broadcast file as one */
13248     loadFlag = appData.suppressLoadMoves;
13249
13250     while (cm == Comment) {
13251         char *p;
13252         if (appData.debugMode)
13253           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13254         p = yy_text;
13255         AppendComment(currentMove, p, FALSE);
13256         yyboardindex = forwardMostMove;
13257         cm = (ChessMove) Myylex();
13258     }
13259
13260     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13261         cm == WhiteWins || cm == BlackWins ||
13262         cm == GameIsDrawn || cm == GameUnfinished) {
13263         DisplayMessage("", _("No moves in game"));
13264         if (cmailMsgLoaded) {
13265             if (appData.debugMode)
13266               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13267             ClearHighlights();
13268             flipView = FALSE;
13269         }
13270         DrawPosition(FALSE, boards[currentMove]);
13271         DisplayBothClocks();
13272         gameMode = EditGame;
13273         ModeHighlight();
13274         gameFileFP = NULL;
13275         cmailOldMove = 0;
13276         return TRUE;
13277     }
13278
13279     // [HGM] PV info: routine tests if comment empty
13280     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13281         DisplayComment(currentMove - 1, commentList[currentMove]);
13282     }
13283     if (!matchMode && appData.timeDelay != 0)
13284       DrawPosition(FALSE, boards[currentMove]);
13285
13286     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13287       programStats.ok_to_send = 1;
13288     }
13289
13290     /* if the first token after the PGN tags is a move
13291      * and not move number 1, retrieve it from the parser
13292      */
13293     if (cm != MoveNumberOne)
13294         LoadGameOneMove(cm);
13295
13296     /* load the remaining moves from the file */
13297     while (LoadGameOneMove(EndOfFile)) {
13298       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13299       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13300     }
13301
13302     /* rewind to the start of the game */
13303     currentMove = backwardMostMove;
13304
13305     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13306
13307     if (oldGameMode == AnalyzeFile) {
13308       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13309       AnalyzeFileEvent();
13310     } else
13311     if (oldGameMode == AnalyzeMode) {
13312       AnalyzeFileEvent();
13313     }
13314
13315     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13316         long int w, b; // [HGM] adjourn: restore saved clock times
13317         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13318         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13319             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13320             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13321         }
13322     }
13323
13324     if(creatingBook) return TRUE;
13325     if (!matchMode && pos > 0) {
13326         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13327     } else
13328     if (matchMode || appData.timeDelay == 0) {
13329       ToEndEvent();
13330     } else if (appData.timeDelay > 0) {
13331       AutoPlayGameLoop();
13332     }
13333
13334     if (appData.debugMode)
13335         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13336
13337     loadFlag = 0; /* [HGM] true game starts */
13338     return TRUE;
13339 }
13340
13341 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13342 int
13343 ReloadPosition (int offset)
13344 {
13345     int positionNumber = lastLoadPositionNumber + offset;
13346     if (lastLoadPositionFP == NULL) {
13347         DisplayError(_("No position has been loaded yet"), 0);
13348         return FALSE;
13349     }
13350     if (positionNumber <= 0) {
13351         DisplayError(_("Can't back up any further"), 0);
13352         return FALSE;
13353     }
13354     return LoadPosition(lastLoadPositionFP, positionNumber,
13355                         lastLoadPositionTitle);
13356 }
13357
13358 /* Load the nth position from the given file */
13359 int
13360 LoadPositionFromFile (char *filename, int n, char *title)
13361 {
13362     FILE *f;
13363     char buf[MSG_SIZ];
13364
13365     if (strcmp(filename, "-") == 0) {
13366         return LoadPosition(stdin, n, "stdin");
13367     } else {
13368         f = fopen(filename, "rb");
13369         if (f == NULL) {
13370             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13371             DisplayError(buf, errno);
13372             return FALSE;
13373         } else {
13374             return LoadPosition(f, n, title);
13375         }
13376     }
13377 }
13378
13379 /* Load the nth position from the given open file, and close it */
13380 int
13381 LoadPosition (FILE *f, int positionNumber, char *title)
13382 {
13383     char *p, line[MSG_SIZ];
13384     Board initial_position;
13385     int i, j, fenMode, pn;
13386
13387     if (gameMode == Training )
13388         SetTrainingModeOff();
13389
13390     if (gameMode != BeginningOfGame) {
13391         Reset(FALSE, TRUE);
13392     }
13393     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13394         fclose(lastLoadPositionFP);
13395     }
13396     if (positionNumber == 0) positionNumber = 1;
13397     lastLoadPositionFP = f;
13398     lastLoadPositionNumber = positionNumber;
13399     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13400     if (first.pr == NoProc && !appData.noChessProgram) {
13401       StartChessProgram(&first);
13402       InitChessProgram(&first, FALSE);
13403     }
13404     pn = positionNumber;
13405     if (positionNumber < 0) {
13406         /* Negative position number means to seek to that byte offset */
13407         if (fseek(f, -positionNumber, 0) == -1) {
13408             DisplayError(_("Can't seek on position file"), 0);
13409             return FALSE;
13410         };
13411         pn = 1;
13412     } else {
13413         if (fseek(f, 0, 0) == -1) {
13414             if (f == lastLoadPositionFP ?
13415                 positionNumber == lastLoadPositionNumber + 1 :
13416                 positionNumber == 1) {
13417                 pn = 1;
13418             } else {
13419                 DisplayError(_("Can't seek on position file"), 0);
13420                 return FALSE;
13421             }
13422         }
13423     }
13424     /* See if this file is FEN or old-style xboard */
13425     if (fgets(line, MSG_SIZ, f) == NULL) {
13426         DisplayError(_("Position not found in file"), 0);
13427         return FALSE;
13428     }
13429     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13430     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13431
13432     if (pn >= 2) {
13433         if (fenMode || line[0] == '#') pn--;
13434         while (pn > 0) {
13435             /* skip positions before number pn */
13436             if (fgets(line, MSG_SIZ, f) == NULL) {
13437                 Reset(TRUE, TRUE);
13438                 DisplayError(_("Position not found in file"), 0);
13439                 return FALSE;
13440             }
13441             if (fenMode || line[0] == '#') pn--;
13442         }
13443     }
13444
13445     if (fenMode) {
13446         char *p;
13447         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13448             DisplayError(_("Bad FEN position in file"), 0);
13449             return FALSE;
13450         }
13451         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13452             sscanf(p+3, "%s", bestMove);
13453         } else *bestMove = NULLCHAR;
13454     } else {
13455         (void) fgets(line, MSG_SIZ, f);
13456         (void) fgets(line, MSG_SIZ, f);
13457
13458         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13459             (void) fgets(line, MSG_SIZ, f);
13460             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13461                 if (*p == ' ')
13462                   continue;
13463                 initial_position[i][j++] = CharToPiece(*p);
13464             }
13465         }
13466
13467         blackPlaysFirst = FALSE;
13468         if (!feof(f)) {
13469             (void) fgets(line, MSG_SIZ, f);
13470             if (strncmp(line, "black", strlen("black"))==0)
13471               blackPlaysFirst = TRUE;
13472         }
13473     }
13474     startedFromSetupPosition = TRUE;
13475
13476     CopyBoard(boards[0], initial_position);
13477     if (blackPlaysFirst) {
13478         currentMove = forwardMostMove = backwardMostMove = 1;
13479         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13480         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13481         CopyBoard(boards[1], initial_position);
13482         DisplayMessage("", _("Black to play"));
13483     } else {
13484         currentMove = forwardMostMove = backwardMostMove = 0;
13485         DisplayMessage("", _("White to play"));
13486     }
13487     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13488     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13489         SendToProgram("force\n", &first);
13490         SendBoard(&first, forwardMostMove);
13491     }
13492     if (appData.debugMode) {
13493 int i, j;
13494   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13495   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13496         fprintf(debugFP, "Load Position\n");
13497     }
13498
13499     if (positionNumber > 1) {
13500       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13501         DisplayTitle(line);
13502     } else {
13503         DisplayTitle(title);
13504     }
13505     gameMode = EditGame;
13506     ModeHighlight();
13507     ResetClocks();
13508     timeRemaining[0][1] = whiteTimeRemaining;
13509     timeRemaining[1][1] = blackTimeRemaining;
13510     DrawPosition(FALSE, boards[currentMove]);
13511
13512     return TRUE;
13513 }
13514
13515
13516 void
13517 CopyPlayerNameIntoFileName (char **dest, char *src)
13518 {
13519     while (*src != NULLCHAR && *src != ',') {
13520         if (*src == ' ') {
13521             *(*dest)++ = '_';
13522             src++;
13523         } else {
13524             *(*dest)++ = *src++;
13525         }
13526     }
13527 }
13528
13529 char *
13530 DefaultFileName (char *ext)
13531 {
13532     static char def[MSG_SIZ];
13533     char *p;
13534
13535     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13536         p = def;
13537         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13538         *p++ = '-';
13539         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13540         *p++ = '.';
13541         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13542     } else {
13543         def[0] = NULLCHAR;
13544     }
13545     return def;
13546 }
13547
13548 /* Save the current game to the given file */
13549 int
13550 SaveGameToFile (char *filename, int append)
13551 {
13552     FILE *f;
13553     char buf[MSG_SIZ];
13554     int result, i, t,tot=0;
13555
13556     if (strcmp(filename, "-") == 0) {
13557         return SaveGame(stdout, 0, NULL);
13558     } else {
13559         for(i=0; i<10; i++) { // upto 10 tries
13560              f = fopen(filename, append ? "a" : "w");
13561              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13562              if(f || errno != 13) break;
13563              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13564              tot += t;
13565         }
13566         if (f == NULL) {
13567             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13568             DisplayError(buf, errno);
13569             return FALSE;
13570         } else {
13571             safeStrCpy(buf, lastMsg, MSG_SIZ);
13572             DisplayMessage(_("Waiting for access to save file"), "");
13573             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13574             DisplayMessage(_("Saving game"), "");
13575             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13576             result = SaveGame(f, 0, NULL);
13577             DisplayMessage(buf, "");
13578             return result;
13579         }
13580     }
13581 }
13582
13583 char *
13584 SavePart (char *str)
13585 {
13586     static char buf[MSG_SIZ];
13587     char *p;
13588
13589     p = strchr(str, ' ');
13590     if (p == NULL) return str;
13591     strncpy(buf, str, p - str);
13592     buf[p - str] = NULLCHAR;
13593     return buf;
13594 }
13595
13596 #define PGN_MAX_LINE 75
13597
13598 #define PGN_SIDE_WHITE  0
13599 #define PGN_SIDE_BLACK  1
13600
13601 static int
13602 FindFirstMoveOutOfBook (int side)
13603 {
13604     int result = -1;
13605
13606     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13607         int index = backwardMostMove;
13608         int has_book_hit = 0;
13609
13610         if( (index % 2) != side ) {
13611             index++;
13612         }
13613
13614         while( index < forwardMostMove ) {
13615             /* Check to see if engine is in book */
13616             int depth = pvInfoList[index].depth;
13617             int score = pvInfoList[index].score;
13618             int in_book = 0;
13619
13620             if( depth <= 2 ) {
13621                 in_book = 1;
13622             }
13623             else if( score == 0 && depth == 63 ) {
13624                 in_book = 1; /* Zappa */
13625             }
13626             else if( score == 2 && depth == 99 ) {
13627                 in_book = 1; /* Abrok */
13628             }
13629
13630             has_book_hit += in_book;
13631
13632             if( ! in_book ) {
13633                 result = index;
13634
13635                 break;
13636             }
13637
13638             index += 2;
13639         }
13640     }
13641
13642     return result;
13643 }
13644
13645 void
13646 GetOutOfBookInfo (char * buf)
13647 {
13648     int oob[2];
13649     int i;
13650     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13651
13652     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13653     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13654
13655     *buf = '\0';
13656
13657     if( oob[0] >= 0 || oob[1] >= 0 ) {
13658         for( i=0; i<2; i++ ) {
13659             int idx = oob[i];
13660
13661             if( idx >= 0 ) {
13662                 if( i > 0 && oob[0] >= 0 ) {
13663                     strcat( buf, "   " );
13664                 }
13665
13666                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13667                 sprintf( buf+strlen(buf), "%s%.2f",
13668                     pvInfoList[idx].score >= 0 ? "+" : "",
13669                     pvInfoList[idx].score / 100.0 );
13670             }
13671         }
13672     }
13673 }
13674
13675 /* Save game in PGN style */
13676 static void
13677 SaveGamePGN2 (FILE *f)
13678 {
13679     int i, offset, linelen, newblock;
13680 //    char *movetext;
13681     char numtext[32];
13682     int movelen, numlen, blank;
13683     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13684
13685     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13686
13687     PrintPGNTags(f, &gameInfo);
13688
13689     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13690
13691     if (backwardMostMove > 0 || startedFromSetupPosition) {
13692         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13693         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13694         fprintf(f, "\n{--------------\n");
13695         PrintPosition(f, backwardMostMove);
13696         fprintf(f, "--------------}\n");
13697         free(fen);
13698     }
13699     else {
13700         /* [AS] Out of book annotation */
13701         if( appData.saveOutOfBookInfo ) {
13702             char buf[64];
13703
13704             GetOutOfBookInfo( buf );
13705
13706             if( buf[0] != '\0' ) {
13707                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13708             }
13709         }
13710
13711         fprintf(f, "\n");
13712     }
13713
13714     i = backwardMostMove;
13715     linelen = 0;
13716     newblock = TRUE;
13717
13718     while (i < forwardMostMove) {
13719         /* Print comments preceding this move */
13720         if (commentList[i] != NULL) {
13721             if (linelen > 0) fprintf(f, "\n");
13722             fprintf(f, "%s", commentList[i]);
13723             linelen = 0;
13724             newblock = TRUE;
13725         }
13726
13727         /* Format move number */
13728         if ((i % 2) == 0)
13729           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13730         else
13731           if (newblock)
13732             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13733           else
13734             numtext[0] = NULLCHAR;
13735
13736         numlen = strlen(numtext);
13737         newblock = FALSE;
13738
13739         /* Print move number */
13740         blank = linelen > 0 && numlen > 0;
13741         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13742             fprintf(f, "\n");
13743             linelen = 0;
13744             blank = 0;
13745         }
13746         if (blank) {
13747             fprintf(f, " ");
13748             linelen++;
13749         }
13750         fprintf(f, "%s", numtext);
13751         linelen += numlen;
13752
13753         /* Get move */
13754         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13755         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13756
13757         /* Print move */
13758         blank = linelen > 0 && movelen > 0;
13759         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13760             fprintf(f, "\n");
13761             linelen = 0;
13762             blank = 0;
13763         }
13764         if (blank) {
13765             fprintf(f, " ");
13766             linelen++;
13767         }
13768         fprintf(f, "%s", move_buffer);
13769         linelen += movelen;
13770
13771         /* [AS] Add PV info if present */
13772         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13773             /* [HGM] add time */
13774             char buf[MSG_SIZ]; int seconds;
13775
13776             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13777
13778             if( seconds <= 0)
13779               buf[0] = 0;
13780             else
13781               if( seconds < 30 )
13782                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13783               else
13784                 {
13785                   seconds = (seconds + 4)/10; // round to full seconds
13786                   if( seconds < 60 )
13787                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13788                   else
13789                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13790                 }
13791
13792             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13793                       pvInfoList[i].score >= 0 ? "+" : "",
13794                       pvInfoList[i].score / 100.0,
13795                       pvInfoList[i].depth,
13796                       buf );
13797
13798             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13799
13800             /* Print score/depth */
13801             blank = linelen > 0 && movelen > 0;
13802             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13803                 fprintf(f, "\n");
13804                 linelen = 0;
13805                 blank = 0;
13806             }
13807             if (blank) {
13808                 fprintf(f, " ");
13809                 linelen++;
13810             }
13811             fprintf(f, "%s", move_buffer);
13812             linelen += movelen;
13813         }
13814
13815         i++;
13816     }
13817
13818     /* Start a new line */
13819     if (linelen > 0) fprintf(f, "\n");
13820
13821     /* Print comments after last move */
13822     if (commentList[i] != NULL) {
13823         fprintf(f, "%s\n", commentList[i]);
13824     }
13825
13826     /* Print result */
13827     if (gameInfo.resultDetails != NULL &&
13828         gameInfo.resultDetails[0] != NULLCHAR) {
13829         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13830         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13831            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13832             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13833         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13834     } else {
13835         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13836     }
13837 }
13838
13839 /* Save game in PGN style and close the file */
13840 int
13841 SaveGamePGN (FILE *f)
13842 {
13843     SaveGamePGN2(f);
13844     fclose(f);
13845     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13846     return TRUE;
13847 }
13848
13849 /* Save game in old style and close the file */
13850 int
13851 SaveGameOldStyle (FILE *f)
13852 {
13853     int i, offset;
13854     time_t tm;
13855
13856     tm = time((time_t *) NULL);
13857
13858     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13859     PrintOpponents(f);
13860
13861     if (backwardMostMove > 0 || startedFromSetupPosition) {
13862         fprintf(f, "\n[--------------\n");
13863         PrintPosition(f, backwardMostMove);
13864         fprintf(f, "--------------]\n");
13865     } else {
13866         fprintf(f, "\n");
13867     }
13868
13869     i = backwardMostMove;
13870     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13871
13872     while (i < forwardMostMove) {
13873         if (commentList[i] != NULL) {
13874             fprintf(f, "[%s]\n", commentList[i]);
13875         }
13876
13877         if ((i % 2) == 1) {
13878             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13879             i++;
13880         } else {
13881             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13882             i++;
13883             if (commentList[i] != NULL) {
13884                 fprintf(f, "\n");
13885                 continue;
13886             }
13887             if (i >= forwardMostMove) {
13888                 fprintf(f, "\n");
13889                 break;
13890             }
13891             fprintf(f, "%s\n", parseList[i]);
13892             i++;
13893         }
13894     }
13895
13896     if (commentList[i] != NULL) {
13897         fprintf(f, "[%s]\n", commentList[i]);
13898     }
13899
13900     /* This isn't really the old style, but it's close enough */
13901     if (gameInfo.resultDetails != NULL &&
13902         gameInfo.resultDetails[0] != NULLCHAR) {
13903         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13904                 gameInfo.resultDetails);
13905     } else {
13906         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13907     }
13908
13909     fclose(f);
13910     return TRUE;
13911 }
13912
13913 /* Save the current game to open file f and close the file */
13914 int
13915 SaveGame (FILE *f, int dummy, char *dummy2)
13916 {
13917     if (gameMode == EditPosition) EditPositionDone(TRUE);
13918     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13919     if (appData.oldSaveStyle)
13920       return SaveGameOldStyle(f);
13921     else
13922       return SaveGamePGN(f);
13923 }
13924
13925 /* Save the current position to the given file */
13926 int
13927 SavePositionToFile (char *filename)
13928 {
13929     FILE *f;
13930     char buf[MSG_SIZ];
13931
13932     if (strcmp(filename, "-") == 0) {
13933         return SavePosition(stdout, 0, NULL);
13934     } else {
13935         f = fopen(filename, "a");
13936         if (f == NULL) {
13937             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13938             DisplayError(buf, errno);
13939             return FALSE;
13940         } else {
13941             safeStrCpy(buf, lastMsg, MSG_SIZ);
13942             DisplayMessage(_("Waiting for access to save file"), "");
13943             flock(fileno(f), LOCK_EX); // [HGM] lock
13944             DisplayMessage(_("Saving position"), "");
13945             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13946             SavePosition(f, 0, NULL);
13947             DisplayMessage(buf, "");
13948             return TRUE;
13949         }
13950     }
13951 }
13952
13953 /* Save the current position to the given open file and close the file */
13954 int
13955 SavePosition (FILE *f, int dummy, char *dummy2)
13956 {
13957     time_t tm;
13958     char *fen;
13959
13960     if (gameMode == EditPosition) EditPositionDone(TRUE);
13961     if (appData.oldSaveStyle) {
13962         tm = time((time_t *) NULL);
13963
13964         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13965         PrintOpponents(f);
13966         fprintf(f, "[--------------\n");
13967         PrintPosition(f, currentMove);
13968         fprintf(f, "--------------]\n");
13969     } else {
13970         fen = PositionToFEN(currentMove, NULL, 1);
13971         fprintf(f, "%s\n", fen);
13972         free(fen);
13973     }
13974     fclose(f);
13975     return TRUE;
13976 }
13977
13978 void
13979 ReloadCmailMsgEvent (int unregister)
13980 {
13981 #if !WIN32
13982     static char *inFilename = NULL;
13983     static char *outFilename;
13984     int i;
13985     struct stat inbuf, outbuf;
13986     int status;
13987
13988     /* Any registered moves are unregistered if unregister is set, */
13989     /* i.e. invoked by the signal handler */
13990     if (unregister) {
13991         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13992             cmailMoveRegistered[i] = FALSE;
13993             if (cmailCommentList[i] != NULL) {
13994                 free(cmailCommentList[i]);
13995                 cmailCommentList[i] = NULL;
13996             }
13997         }
13998         nCmailMovesRegistered = 0;
13999     }
14000
14001     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14002         cmailResult[i] = CMAIL_NOT_RESULT;
14003     }
14004     nCmailResults = 0;
14005
14006     if (inFilename == NULL) {
14007         /* Because the filenames are static they only get malloced once  */
14008         /* and they never get freed                                      */
14009         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14010         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14011
14012         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14013         sprintf(outFilename, "%s.out", appData.cmailGameName);
14014     }
14015
14016     status = stat(outFilename, &outbuf);
14017     if (status < 0) {
14018         cmailMailedMove = FALSE;
14019     } else {
14020         status = stat(inFilename, &inbuf);
14021         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14022     }
14023
14024     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14025        counts the games, notes how each one terminated, etc.
14026
14027        It would be nice to remove this kludge and instead gather all
14028        the information while building the game list.  (And to keep it
14029        in the game list nodes instead of having a bunch of fixed-size
14030        parallel arrays.)  Note this will require getting each game's
14031        termination from the PGN tags, as the game list builder does
14032        not process the game moves.  --mann
14033        */
14034     cmailMsgLoaded = TRUE;
14035     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14036
14037     /* Load first game in the file or popup game menu */
14038     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14039
14040 #endif /* !WIN32 */
14041     return;
14042 }
14043
14044 int
14045 RegisterMove ()
14046 {
14047     FILE *f;
14048     char string[MSG_SIZ];
14049
14050     if (   cmailMailedMove
14051         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14052         return TRUE;            /* Allow free viewing  */
14053     }
14054
14055     /* Unregister move to ensure that we don't leave RegisterMove        */
14056     /* with the move registered when the conditions for registering no   */
14057     /* longer hold                                                       */
14058     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14059         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14060         nCmailMovesRegistered --;
14061
14062         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14063           {
14064               free(cmailCommentList[lastLoadGameNumber - 1]);
14065               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14066           }
14067     }
14068
14069     if (cmailOldMove == -1) {
14070         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14071         return FALSE;
14072     }
14073
14074     if (currentMove > cmailOldMove + 1) {
14075         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14076         return FALSE;
14077     }
14078
14079     if (currentMove < cmailOldMove) {
14080         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14081         return FALSE;
14082     }
14083
14084     if (forwardMostMove > currentMove) {
14085         /* Silently truncate extra moves */
14086         TruncateGame();
14087     }
14088
14089     if (   (currentMove == cmailOldMove + 1)
14090         || (   (currentMove == cmailOldMove)
14091             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14092                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14093         if (gameInfo.result != GameUnfinished) {
14094             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14095         }
14096
14097         if (commentList[currentMove] != NULL) {
14098             cmailCommentList[lastLoadGameNumber - 1]
14099               = StrSave(commentList[currentMove]);
14100         }
14101         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14102
14103         if (appData.debugMode)
14104           fprintf(debugFP, "Saving %s for game %d\n",
14105                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14106
14107         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14108
14109         f = fopen(string, "w");
14110         if (appData.oldSaveStyle) {
14111             SaveGameOldStyle(f); /* also closes the file */
14112
14113             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14114             f = fopen(string, "w");
14115             SavePosition(f, 0, NULL); /* also closes the file */
14116         } else {
14117             fprintf(f, "{--------------\n");
14118             PrintPosition(f, currentMove);
14119             fprintf(f, "--------------}\n\n");
14120
14121             SaveGame(f, 0, NULL); /* also closes the file*/
14122         }
14123
14124         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14125         nCmailMovesRegistered ++;
14126     } else if (nCmailGames == 1) {
14127         DisplayError(_("You have not made a move yet"), 0);
14128         return FALSE;
14129     }
14130
14131     return TRUE;
14132 }
14133
14134 void
14135 MailMoveEvent ()
14136 {
14137 #if !WIN32
14138     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14139     FILE *commandOutput;
14140     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14141     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14142     int nBuffers;
14143     int i;
14144     int archived;
14145     char *arcDir;
14146
14147     if (! cmailMsgLoaded) {
14148         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14149         return;
14150     }
14151
14152     if (nCmailGames == nCmailResults) {
14153         DisplayError(_("No unfinished games"), 0);
14154         return;
14155     }
14156
14157 #if CMAIL_PROHIBIT_REMAIL
14158     if (cmailMailedMove) {
14159       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);
14160         DisplayError(msg, 0);
14161         return;
14162     }
14163 #endif
14164
14165     if (! (cmailMailedMove || RegisterMove())) return;
14166
14167     if (   cmailMailedMove
14168         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14169       snprintf(string, MSG_SIZ, partCommandString,
14170                appData.debugMode ? " -v" : "", appData.cmailGameName);
14171         commandOutput = popen(string, "r");
14172
14173         if (commandOutput == NULL) {
14174             DisplayError(_("Failed to invoke cmail"), 0);
14175         } else {
14176             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14177                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14178             }
14179             if (nBuffers > 1) {
14180                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14181                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14182                 nBytes = MSG_SIZ - 1;
14183             } else {
14184                 (void) memcpy(msg, buffer, nBytes);
14185             }
14186             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14187
14188             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14189                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14190
14191                 archived = TRUE;
14192                 for (i = 0; i < nCmailGames; i ++) {
14193                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14194                         archived = FALSE;
14195                     }
14196                 }
14197                 if (   archived
14198                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14199                         != NULL)) {
14200                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14201                            arcDir,
14202                            appData.cmailGameName,
14203                            gameInfo.date);
14204                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14205                     cmailMsgLoaded = FALSE;
14206                 }
14207             }
14208
14209             DisplayInformation(msg);
14210             pclose(commandOutput);
14211         }
14212     } else {
14213         if ((*cmailMsg) != '\0') {
14214             DisplayInformation(cmailMsg);
14215         }
14216     }
14217
14218     return;
14219 #endif /* !WIN32 */
14220 }
14221
14222 char *
14223 CmailMsg ()
14224 {
14225 #if WIN32
14226     return NULL;
14227 #else
14228     int  prependComma = 0;
14229     char number[5];
14230     char string[MSG_SIZ];       /* Space for game-list */
14231     int  i;
14232
14233     if (!cmailMsgLoaded) return "";
14234
14235     if (cmailMailedMove) {
14236       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14237     } else {
14238         /* Create a list of games left */
14239       snprintf(string, MSG_SIZ, "[");
14240         for (i = 0; i < nCmailGames; i ++) {
14241             if (! (   cmailMoveRegistered[i]
14242                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14243                 if (prependComma) {
14244                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14245                 } else {
14246                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14247                     prependComma = 1;
14248                 }
14249
14250                 strcat(string, number);
14251             }
14252         }
14253         strcat(string, "]");
14254
14255         if (nCmailMovesRegistered + nCmailResults == 0) {
14256             switch (nCmailGames) {
14257               case 1:
14258                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14259                 break;
14260
14261               case 2:
14262                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14263                 break;
14264
14265               default:
14266                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14267                          nCmailGames);
14268                 break;
14269             }
14270         } else {
14271             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14272               case 1:
14273                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14274                          string);
14275                 break;
14276
14277               case 0:
14278                 if (nCmailResults == nCmailGames) {
14279                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14280                 } else {
14281                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14282                 }
14283                 break;
14284
14285               default:
14286                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14287                          string);
14288             }
14289         }
14290     }
14291     return cmailMsg;
14292 #endif /* WIN32 */
14293 }
14294
14295 void
14296 ResetGameEvent ()
14297 {
14298     if (gameMode == Training)
14299       SetTrainingModeOff();
14300
14301     Reset(TRUE, TRUE);
14302     cmailMsgLoaded = FALSE;
14303     if (appData.icsActive) {
14304       SendToICS(ics_prefix);
14305       SendToICS("refresh\n");
14306     }
14307 }
14308
14309 void
14310 ExitEvent (int status)
14311 {
14312     exiting++;
14313     if (exiting > 2) {
14314       /* Give up on clean exit */
14315       exit(status);
14316     }
14317     if (exiting > 1) {
14318       /* Keep trying for clean exit */
14319       return;
14320     }
14321
14322     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14323     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14324
14325     if (telnetISR != NULL) {
14326       RemoveInputSource(telnetISR);
14327     }
14328     if (icsPR != NoProc) {
14329       DestroyChildProcess(icsPR, TRUE);
14330     }
14331
14332     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14333     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14334
14335     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14336     /* make sure this other one finishes before killing it!                  */
14337     if(endingGame) { int count = 0;
14338         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14339         while(endingGame && count++ < 10) DoSleep(1);
14340         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14341     }
14342
14343     /* Kill off chess programs */
14344     if (first.pr != NoProc) {
14345         ExitAnalyzeMode();
14346
14347         DoSleep( appData.delayBeforeQuit );
14348         SendToProgram("quit\n", &first);
14349         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14350     }
14351     if (second.pr != NoProc) {
14352         DoSleep( appData.delayBeforeQuit );
14353         SendToProgram("quit\n", &second);
14354         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14355     }
14356     if (first.isr != NULL) {
14357         RemoveInputSource(first.isr);
14358     }
14359     if (second.isr != NULL) {
14360         RemoveInputSource(second.isr);
14361     }
14362
14363     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14364     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14365
14366     ShutDownFrontEnd();
14367     exit(status);
14368 }
14369
14370 void
14371 PauseEngine (ChessProgramState *cps)
14372 {
14373     SendToProgram("pause\n", cps);
14374     cps->pause = 2;
14375 }
14376
14377 void
14378 UnPauseEngine (ChessProgramState *cps)
14379 {
14380     SendToProgram("resume\n", cps);
14381     cps->pause = 1;
14382 }
14383
14384 void
14385 PauseEvent ()
14386 {
14387     if (appData.debugMode)
14388         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14389     if (pausing) {
14390         pausing = FALSE;
14391         ModeHighlight();
14392         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14393             StartClocks();
14394             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14395                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14396                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14397             }
14398             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14399             HandleMachineMove(stashedInputMove, stalledEngine);
14400             stalledEngine = NULL;
14401             return;
14402         }
14403         if (gameMode == MachinePlaysWhite ||
14404             gameMode == TwoMachinesPlay   ||
14405             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14406             if(first.pause)  UnPauseEngine(&first);
14407             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14408             if(second.pause) UnPauseEngine(&second);
14409             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14410             StartClocks();
14411         } else {
14412             DisplayBothClocks();
14413         }
14414         if (gameMode == PlayFromGameFile) {
14415             if (appData.timeDelay >= 0)
14416                 AutoPlayGameLoop();
14417         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14418             Reset(FALSE, TRUE);
14419             SendToICS(ics_prefix);
14420             SendToICS("refresh\n");
14421         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14422             ForwardInner(forwardMostMove);
14423         }
14424         pauseExamInvalid = FALSE;
14425     } else {
14426         switch (gameMode) {
14427           default:
14428             return;
14429           case IcsExamining:
14430             pauseExamForwardMostMove = forwardMostMove;
14431             pauseExamInvalid = FALSE;
14432             /* fall through */
14433           case IcsObserving:
14434           case IcsPlayingWhite:
14435           case IcsPlayingBlack:
14436             pausing = TRUE;
14437             ModeHighlight();
14438             return;
14439           case PlayFromGameFile:
14440             (void) StopLoadGameTimer();
14441             pausing = TRUE;
14442             ModeHighlight();
14443             break;
14444           case BeginningOfGame:
14445             if (appData.icsActive) return;
14446             /* else fall through */
14447           case MachinePlaysWhite:
14448           case MachinePlaysBlack:
14449           case TwoMachinesPlay:
14450             if (forwardMostMove == 0)
14451               return;           /* don't pause if no one has moved */
14452             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14453                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14454                 if(onMove->pause) {           // thinking engine can be paused
14455                     PauseEngine(onMove);      // do it
14456                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14457                         PauseEngine(onMove->other);
14458                     else
14459                         SendToProgram("easy\n", onMove->other);
14460                     StopClocks();
14461                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14462             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14463                 if(first.pause) {
14464                     PauseEngine(&first);
14465                     StopClocks();
14466                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14467             } else { // human on move, pause pondering by either method
14468                 if(first.pause)
14469                     PauseEngine(&first);
14470                 else if(appData.ponderNextMove)
14471                     SendToProgram("easy\n", &first);
14472                 StopClocks();
14473             }
14474             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14475           case AnalyzeMode:
14476             pausing = TRUE;
14477             ModeHighlight();
14478             break;
14479         }
14480     }
14481 }
14482
14483 void
14484 EditCommentEvent ()
14485 {
14486     char title[MSG_SIZ];
14487
14488     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14489       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14490     } else {
14491       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14492                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14493                parseList[currentMove - 1]);
14494     }
14495
14496     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14497 }
14498
14499
14500 void
14501 EditTagsEvent ()
14502 {
14503     char *tags = PGNTags(&gameInfo);
14504     bookUp = FALSE;
14505     EditTagsPopUp(tags, NULL);
14506     free(tags);
14507 }
14508
14509 void
14510 ToggleSecond ()
14511 {
14512   if(second.analyzing) {
14513     SendToProgram("exit\n", &second);
14514     second.analyzing = FALSE;
14515   } else {
14516     if (second.pr == NoProc) StartChessProgram(&second);
14517     InitChessProgram(&second, FALSE);
14518     FeedMovesToProgram(&second, currentMove);
14519
14520     SendToProgram("analyze\n", &second);
14521     second.analyzing = TRUE;
14522   }
14523 }
14524
14525 /* Toggle ShowThinking */
14526 void
14527 ToggleShowThinking()
14528 {
14529   appData.showThinking = !appData.showThinking;
14530   ShowThinkingEvent();
14531 }
14532
14533 int
14534 AnalyzeModeEvent ()
14535 {
14536     char buf[MSG_SIZ];
14537
14538     if (!first.analysisSupport) {
14539       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14540       DisplayError(buf, 0);
14541       return 0;
14542     }
14543     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14544     if (appData.icsActive) {
14545         if (gameMode != IcsObserving) {
14546           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14547             DisplayError(buf, 0);
14548             /* secure check */
14549             if (appData.icsEngineAnalyze) {
14550                 if (appData.debugMode)
14551                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14552                 ExitAnalyzeMode();
14553                 ModeHighlight();
14554             }
14555             return 0;
14556         }
14557         /* if enable, user wants to disable icsEngineAnalyze */
14558         if (appData.icsEngineAnalyze) {
14559                 ExitAnalyzeMode();
14560                 ModeHighlight();
14561                 return 0;
14562         }
14563         appData.icsEngineAnalyze = TRUE;
14564         if (appData.debugMode)
14565             fprintf(debugFP, "ICS engine analyze starting... \n");
14566     }
14567
14568     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14569     if (appData.noChessProgram || gameMode == AnalyzeMode)
14570       return 0;
14571
14572     if (gameMode != AnalyzeFile) {
14573         if (!appData.icsEngineAnalyze) {
14574                EditGameEvent();
14575                if (gameMode != EditGame) return 0;
14576         }
14577         if (!appData.showThinking) ToggleShowThinking();
14578         ResurrectChessProgram();
14579         SendToProgram("analyze\n", &first);
14580         first.analyzing = TRUE;
14581         /*first.maybeThinking = TRUE;*/
14582         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14583         EngineOutputPopUp();
14584     }
14585     if (!appData.icsEngineAnalyze) {
14586         gameMode = AnalyzeMode;
14587         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14588     }
14589     pausing = FALSE;
14590     ModeHighlight();
14591     SetGameInfo();
14592
14593     StartAnalysisClock();
14594     GetTimeMark(&lastNodeCountTime);
14595     lastNodeCount = 0;
14596     return 1;
14597 }
14598
14599 void
14600 AnalyzeFileEvent ()
14601 {
14602     if (appData.noChessProgram || gameMode == AnalyzeFile)
14603       return;
14604
14605     if (!first.analysisSupport) {
14606       char buf[MSG_SIZ];
14607       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14608       DisplayError(buf, 0);
14609       return;
14610     }
14611
14612     if (gameMode != AnalyzeMode) {
14613         keepInfo = 1; // mere annotating should not alter PGN tags
14614         EditGameEvent();
14615         keepInfo = 0;
14616         if (gameMode != EditGame) return;
14617         if (!appData.showThinking) ToggleShowThinking();
14618         ResurrectChessProgram();
14619         SendToProgram("analyze\n", &first);
14620         first.analyzing = TRUE;
14621         /*first.maybeThinking = TRUE;*/
14622         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14623         EngineOutputPopUp();
14624     }
14625     gameMode = AnalyzeFile;
14626     pausing = FALSE;
14627     ModeHighlight();
14628
14629     StartAnalysisClock();
14630     GetTimeMark(&lastNodeCountTime);
14631     lastNodeCount = 0;
14632     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14633     AnalysisPeriodicEvent(1);
14634 }
14635
14636 void
14637 MachineWhiteEvent ()
14638 {
14639     char buf[MSG_SIZ];
14640     char *bookHit = NULL;
14641
14642     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14643       return;
14644
14645
14646     if (gameMode == PlayFromGameFile ||
14647         gameMode == TwoMachinesPlay  ||
14648         gameMode == Training         ||
14649         gameMode == AnalyzeMode      ||
14650         gameMode == EndOfGame)
14651         EditGameEvent();
14652
14653     if (gameMode == EditPosition)
14654         EditPositionDone(TRUE);
14655
14656     if (!WhiteOnMove(currentMove)) {
14657         DisplayError(_("It is not White's turn"), 0);
14658         return;
14659     }
14660
14661     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14662       ExitAnalyzeMode();
14663
14664     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14665         gameMode == AnalyzeFile)
14666         TruncateGame();
14667
14668     ResurrectChessProgram();    /* in case it isn't running */
14669     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14670         gameMode = MachinePlaysWhite;
14671         ResetClocks();
14672     } else
14673     gameMode = MachinePlaysWhite;
14674     pausing = FALSE;
14675     ModeHighlight();
14676     SetGameInfo();
14677     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14678     DisplayTitle(buf);
14679     if (first.sendName) {
14680       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14681       SendToProgram(buf, &first);
14682     }
14683     if (first.sendTime) {
14684       if (first.useColors) {
14685         SendToProgram("black\n", &first); /*gnu kludge*/
14686       }
14687       SendTimeRemaining(&first, TRUE);
14688     }
14689     if (first.useColors) {
14690       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14691     }
14692     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14693     SetMachineThinkingEnables();
14694     first.maybeThinking = TRUE;
14695     StartClocks();
14696     firstMove = FALSE;
14697
14698     if (appData.autoFlipView && !flipView) {
14699       flipView = !flipView;
14700       DrawPosition(FALSE, NULL);
14701       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14702     }
14703
14704     if(bookHit) { // [HGM] book: simulate book reply
14705         static char bookMove[MSG_SIZ]; // a bit generous?
14706
14707         programStats.nodes = programStats.depth = programStats.time =
14708         programStats.score = programStats.got_only_move = 0;
14709         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14710
14711         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14712         strcat(bookMove, bookHit);
14713         HandleMachineMove(bookMove, &first);
14714     }
14715 }
14716
14717 void
14718 MachineBlackEvent ()
14719 {
14720   char buf[MSG_SIZ];
14721   char *bookHit = NULL;
14722
14723     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14724         return;
14725
14726
14727     if (gameMode == PlayFromGameFile ||
14728         gameMode == TwoMachinesPlay  ||
14729         gameMode == Training         ||
14730         gameMode == AnalyzeMode      ||
14731         gameMode == EndOfGame)
14732         EditGameEvent();
14733
14734     if (gameMode == EditPosition)
14735         EditPositionDone(TRUE);
14736
14737     if (WhiteOnMove(currentMove)) {
14738         DisplayError(_("It is not Black's turn"), 0);
14739         return;
14740     }
14741
14742     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14743       ExitAnalyzeMode();
14744
14745     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14746         gameMode == AnalyzeFile)
14747         TruncateGame();
14748
14749     ResurrectChessProgram();    /* in case it isn't running */
14750     gameMode = MachinePlaysBlack;
14751     pausing = FALSE;
14752     ModeHighlight();
14753     SetGameInfo();
14754     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14755     DisplayTitle(buf);
14756     if (first.sendName) {
14757       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14758       SendToProgram(buf, &first);
14759     }
14760     if (first.sendTime) {
14761       if (first.useColors) {
14762         SendToProgram("white\n", &first); /*gnu kludge*/
14763       }
14764       SendTimeRemaining(&first, FALSE);
14765     }
14766     if (first.useColors) {
14767       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14768     }
14769     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14770     SetMachineThinkingEnables();
14771     first.maybeThinking = TRUE;
14772     StartClocks();
14773
14774     if (appData.autoFlipView && flipView) {
14775       flipView = !flipView;
14776       DrawPosition(FALSE, NULL);
14777       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14778     }
14779     if(bookHit) { // [HGM] book: simulate book reply
14780         static char bookMove[MSG_SIZ]; // a bit generous?
14781
14782         programStats.nodes = programStats.depth = programStats.time =
14783         programStats.score = programStats.got_only_move = 0;
14784         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14785
14786         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14787         strcat(bookMove, bookHit);
14788         HandleMachineMove(bookMove, &first);
14789     }
14790 }
14791
14792
14793 void
14794 DisplayTwoMachinesTitle ()
14795 {
14796     char buf[MSG_SIZ];
14797     if (appData.matchGames > 0) {
14798         if(appData.tourneyFile[0]) {
14799           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14800                    gameInfo.white, _("vs."), gameInfo.black,
14801                    nextGame+1, appData.matchGames+1,
14802                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14803         } else
14804         if (first.twoMachinesColor[0] == 'w') {
14805           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14806                    gameInfo.white, _("vs."),  gameInfo.black,
14807                    first.matchWins, second.matchWins,
14808                    matchGame - 1 - (first.matchWins + second.matchWins));
14809         } else {
14810           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14811                    gameInfo.white, _("vs."), gameInfo.black,
14812                    second.matchWins, first.matchWins,
14813                    matchGame - 1 - (first.matchWins + second.matchWins));
14814         }
14815     } else {
14816       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14817     }
14818     DisplayTitle(buf);
14819 }
14820
14821 void
14822 SettingsMenuIfReady ()
14823 {
14824   if (second.lastPing != second.lastPong) {
14825     DisplayMessage("", _("Waiting for second chess program"));
14826     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14827     return;
14828   }
14829   ThawUI();
14830   DisplayMessage("", "");
14831   SettingsPopUp(&second);
14832 }
14833
14834 int
14835 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14836 {
14837     char buf[MSG_SIZ];
14838     if (cps->pr == NoProc) {
14839         StartChessProgram(cps);
14840         if (cps->protocolVersion == 1) {
14841           retry();
14842           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14843         } else {
14844           /* kludge: allow timeout for initial "feature" command */
14845           if(retry != TwoMachinesEventIfReady) FreezeUI();
14846           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14847           DisplayMessage("", buf);
14848           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14849         }
14850         return 1;
14851     }
14852     return 0;
14853 }
14854
14855 void
14856 TwoMachinesEvent P((void))
14857 {
14858     int i;
14859     char buf[MSG_SIZ];
14860     ChessProgramState *onmove;
14861     char *bookHit = NULL;
14862     static int stalling = 0;
14863     TimeMark now;
14864     long wait;
14865
14866     if (appData.noChessProgram) return;
14867
14868     switch (gameMode) {
14869       case TwoMachinesPlay:
14870         return;
14871       case MachinePlaysWhite:
14872       case MachinePlaysBlack:
14873         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14874             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14875             return;
14876         }
14877         /* fall through */
14878       case BeginningOfGame:
14879       case PlayFromGameFile:
14880       case EndOfGame:
14881         EditGameEvent();
14882         if (gameMode != EditGame) return;
14883         break;
14884       case EditPosition:
14885         EditPositionDone(TRUE);
14886         break;
14887       case AnalyzeMode:
14888       case AnalyzeFile:
14889         ExitAnalyzeMode();
14890         break;
14891       case EditGame:
14892       default:
14893         break;
14894     }
14895
14896 //    forwardMostMove = currentMove;
14897     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14898     startingEngine = TRUE;
14899
14900     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14901
14902     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14903     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14904       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14905       return;
14906     }
14907     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14908
14909     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14910                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14911         startingEngine = matchMode = FALSE;
14912         DisplayError("second engine does not play this", 0);
14913         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14914         EditGameEvent(); // switch back to EditGame mode
14915         return;
14916     }
14917
14918     if(!stalling) {
14919       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14920       SendToProgram("force\n", &second);
14921       stalling = 1;
14922       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14923       return;
14924     }
14925     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14926     if(appData.matchPause>10000 || appData.matchPause<10)
14927                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14928     wait = SubtractTimeMarks(&now, &pauseStart);
14929     if(wait < appData.matchPause) {
14930         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14931         return;
14932     }
14933     // we are now committed to starting the game
14934     stalling = 0;
14935     DisplayMessage("", "");
14936     if (startedFromSetupPosition) {
14937         SendBoard(&second, backwardMostMove);
14938     if (appData.debugMode) {
14939         fprintf(debugFP, "Two Machines\n");
14940     }
14941     }
14942     for (i = backwardMostMove; i < forwardMostMove; i++) {
14943         SendMoveToProgram(i, &second);
14944     }
14945
14946     gameMode = TwoMachinesPlay;
14947     pausing = startingEngine = FALSE;
14948     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14949     SetGameInfo();
14950     DisplayTwoMachinesTitle();
14951     firstMove = TRUE;
14952     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14953         onmove = &first;
14954     } else {
14955         onmove = &second;
14956     }
14957     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14958     SendToProgram(first.computerString, &first);
14959     if (first.sendName) {
14960       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14961       SendToProgram(buf, &first);
14962     }
14963     SendToProgram(second.computerString, &second);
14964     if (second.sendName) {
14965       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14966       SendToProgram(buf, &second);
14967     }
14968
14969     ResetClocks();
14970     if (!first.sendTime || !second.sendTime) {
14971         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14972         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14973     }
14974     if (onmove->sendTime) {
14975       if (onmove->useColors) {
14976         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14977       }
14978       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14979     }
14980     if (onmove->useColors) {
14981       SendToProgram(onmove->twoMachinesColor, onmove);
14982     }
14983     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14984 //    SendToProgram("go\n", onmove);
14985     onmove->maybeThinking = TRUE;
14986     SetMachineThinkingEnables();
14987
14988     StartClocks();
14989
14990     if(bookHit) { // [HGM] book: simulate book reply
14991         static char bookMove[MSG_SIZ]; // a bit generous?
14992
14993         programStats.nodes = programStats.depth = programStats.time =
14994         programStats.score = programStats.got_only_move = 0;
14995         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14996
14997         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14998         strcat(bookMove, bookHit);
14999         savedMessage = bookMove; // args for deferred call
15000         savedState = onmove;
15001         ScheduleDelayedEvent(DeferredBookMove, 1);
15002     }
15003 }
15004
15005 void
15006 TrainingEvent ()
15007 {
15008     if (gameMode == Training) {
15009       SetTrainingModeOff();
15010       gameMode = PlayFromGameFile;
15011       DisplayMessage("", _("Training mode off"));
15012     } else {
15013       gameMode = Training;
15014       animateTraining = appData.animate;
15015
15016       /* make sure we are not already at the end of the game */
15017       if (currentMove < forwardMostMove) {
15018         SetTrainingModeOn();
15019         DisplayMessage("", _("Training mode on"));
15020       } else {
15021         gameMode = PlayFromGameFile;
15022         DisplayError(_("Already at end of game"), 0);
15023       }
15024     }
15025     ModeHighlight();
15026 }
15027
15028 void
15029 IcsClientEvent ()
15030 {
15031     if (!appData.icsActive) return;
15032     switch (gameMode) {
15033       case IcsPlayingWhite:
15034       case IcsPlayingBlack:
15035       case IcsObserving:
15036       case IcsIdle:
15037       case BeginningOfGame:
15038       case IcsExamining:
15039         return;
15040
15041       case EditGame:
15042         break;
15043
15044       case EditPosition:
15045         EditPositionDone(TRUE);
15046         break;
15047
15048       case AnalyzeMode:
15049       case AnalyzeFile:
15050         ExitAnalyzeMode();
15051         break;
15052
15053       default:
15054         EditGameEvent();
15055         break;
15056     }
15057
15058     gameMode = IcsIdle;
15059     ModeHighlight();
15060     return;
15061 }
15062
15063 void
15064 EditGameEvent ()
15065 {
15066     int i;
15067
15068     switch (gameMode) {
15069       case Training:
15070         SetTrainingModeOff();
15071         break;
15072       case MachinePlaysWhite:
15073       case MachinePlaysBlack:
15074       case BeginningOfGame:
15075         SendToProgram("force\n", &first);
15076         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15077             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15078                 char buf[MSG_SIZ];
15079                 abortEngineThink = TRUE;
15080                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15081                 SendToProgram(buf, &first);
15082                 DisplayMessage("Aborting engine think", "");
15083                 FreezeUI();
15084             }
15085         }
15086         SetUserThinkingEnables();
15087         break;
15088       case PlayFromGameFile:
15089         (void) StopLoadGameTimer();
15090         if (gameFileFP != NULL) {
15091             gameFileFP = NULL;
15092         }
15093         break;
15094       case EditPosition:
15095         EditPositionDone(TRUE);
15096         break;
15097       case AnalyzeMode:
15098       case AnalyzeFile:
15099         ExitAnalyzeMode();
15100         SendToProgram("force\n", &first);
15101         break;
15102       case TwoMachinesPlay:
15103         GameEnds(EndOfFile, NULL, GE_PLAYER);
15104         ResurrectChessProgram();
15105         SetUserThinkingEnables();
15106         break;
15107       case EndOfGame:
15108         ResurrectChessProgram();
15109         break;
15110       case IcsPlayingBlack:
15111       case IcsPlayingWhite:
15112         DisplayError(_("Warning: You are still playing a game"), 0);
15113         break;
15114       case IcsObserving:
15115         DisplayError(_("Warning: You are still observing a game"), 0);
15116         break;
15117       case IcsExamining:
15118         DisplayError(_("Warning: You are still examining a game"), 0);
15119         break;
15120       case IcsIdle:
15121         break;
15122       case EditGame:
15123       default:
15124         return;
15125     }
15126
15127     pausing = FALSE;
15128     StopClocks();
15129     first.offeredDraw = second.offeredDraw = 0;
15130
15131     if (gameMode == PlayFromGameFile) {
15132         whiteTimeRemaining = timeRemaining[0][currentMove];
15133         blackTimeRemaining = timeRemaining[1][currentMove];
15134         DisplayTitle("");
15135     }
15136
15137     if (gameMode == MachinePlaysWhite ||
15138         gameMode == MachinePlaysBlack ||
15139         gameMode == TwoMachinesPlay ||
15140         gameMode == EndOfGame) {
15141         i = forwardMostMove;
15142         while (i > currentMove) {
15143             SendToProgram("undo\n", &first);
15144             i--;
15145         }
15146         if(!adjustedClock) {
15147         whiteTimeRemaining = timeRemaining[0][currentMove];
15148         blackTimeRemaining = timeRemaining[1][currentMove];
15149         DisplayBothClocks();
15150         }
15151         if (whiteFlag || blackFlag) {
15152             whiteFlag = blackFlag = 0;
15153         }
15154         DisplayTitle("");
15155     }
15156
15157     gameMode = EditGame;
15158     ModeHighlight();
15159     SetGameInfo();
15160 }
15161
15162
15163 void
15164 EditPositionEvent ()
15165 {
15166     if (gameMode == EditPosition) {
15167         EditGameEvent();
15168         return;
15169     }
15170
15171     EditGameEvent();
15172     if (gameMode != EditGame) return;
15173
15174     gameMode = EditPosition;
15175     ModeHighlight();
15176     SetGameInfo();
15177     if (currentMove > 0)
15178       CopyBoard(boards[0], boards[currentMove]);
15179
15180     blackPlaysFirst = !WhiteOnMove(currentMove);
15181     ResetClocks();
15182     currentMove = forwardMostMove = backwardMostMove = 0;
15183     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15184     DisplayMove(-1);
15185     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15186 }
15187
15188 void
15189 ExitAnalyzeMode ()
15190 {
15191     /* [DM] icsEngineAnalyze - possible call from other functions */
15192     if (appData.icsEngineAnalyze) {
15193         appData.icsEngineAnalyze = FALSE;
15194
15195         DisplayMessage("",_("Close ICS engine analyze..."));
15196     }
15197     if (first.analysisSupport && first.analyzing) {
15198       SendToBoth("exit\n");
15199       first.analyzing = second.analyzing = FALSE;
15200     }
15201     thinkOutput[0] = NULLCHAR;
15202 }
15203
15204 void
15205 EditPositionDone (Boolean fakeRights)
15206 {
15207     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15208
15209     startedFromSetupPosition = TRUE;
15210     InitChessProgram(&first, FALSE);
15211     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15212       boards[0][EP_STATUS] = EP_NONE;
15213       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15214       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15215         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15216         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15217       } else boards[0][CASTLING][2] = NoRights;
15218       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15219         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15220         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15221       } else boards[0][CASTLING][5] = NoRights;
15222       if(gameInfo.variant == VariantSChess) {
15223         int i;
15224         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15225           boards[0][VIRGIN][i] = 0;
15226           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15227           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15228         }
15229       }
15230     }
15231     SendToProgram("force\n", &first);
15232     if (blackPlaysFirst) {
15233         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15234         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15235         currentMove = forwardMostMove = backwardMostMove = 1;
15236         CopyBoard(boards[1], boards[0]);
15237     } else {
15238         currentMove = forwardMostMove = backwardMostMove = 0;
15239     }
15240     SendBoard(&first, forwardMostMove);
15241     if (appData.debugMode) {
15242         fprintf(debugFP, "EditPosDone\n");
15243     }
15244     DisplayTitle("");
15245     DisplayMessage("", "");
15246     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15247     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15248     gameMode = EditGame;
15249     ModeHighlight();
15250     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15251     ClearHighlights(); /* [AS] */
15252 }
15253
15254 /* Pause for `ms' milliseconds */
15255 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15256 void
15257 TimeDelay (long ms)
15258 {
15259     TimeMark m1, m2;
15260
15261     GetTimeMark(&m1);
15262     do {
15263         GetTimeMark(&m2);
15264     } while (SubtractTimeMarks(&m2, &m1) < ms);
15265 }
15266
15267 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15268 void
15269 SendMultiLineToICS (char *buf)
15270 {
15271     char temp[MSG_SIZ+1], *p;
15272     int len;
15273
15274     len = strlen(buf);
15275     if (len > MSG_SIZ)
15276       len = MSG_SIZ;
15277
15278     strncpy(temp, buf, len);
15279     temp[len] = 0;
15280
15281     p = temp;
15282     while (*p) {
15283         if (*p == '\n' || *p == '\r')
15284           *p = ' ';
15285         ++p;
15286     }
15287
15288     strcat(temp, "\n");
15289     SendToICS(temp);
15290     SendToPlayer(temp, strlen(temp));
15291 }
15292
15293 void
15294 SetWhiteToPlayEvent ()
15295 {
15296     if (gameMode == EditPosition) {
15297         blackPlaysFirst = FALSE;
15298         DisplayBothClocks();    /* works because currentMove is 0 */
15299     } else if (gameMode == IcsExamining) {
15300         SendToICS(ics_prefix);
15301         SendToICS("tomove white\n");
15302     }
15303 }
15304
15305 void
15306 SetBlackToPlayEvent ()
15307 {
15308     if (gameMode == EditPosition) {
15309         blackPlaysFirst = TRUE;
15310         currentMove = 1;        /* kludge */
15311         DisplayBothClocks();
15312         currentMove = 0;
15313     } else if (gameMode == IcsExamining) {
15314         SendToICS(ics_prefix);
15315         SendToICS("tomove black\n");
15316     }
15317 }
15318
15319 void
15320 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15321 {
15322     char buf[MSG_SIZ];
15323     ChessSquare piece = boards[0][y][x];
15324     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15325     static int lastVariant;
15326
15327     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15328
15329     switch (selection) {
15330       case ClearBoard:
15331         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15332         MarkTargetSquares(1);
15333         CopyBoard(currentBoard, boards[0]);
15334         CopyBoard(menuBoard, initialPosition);
15335         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15336             SendToICS(ics_prefix);
15337             SendToICS("bsetup clear\n");
15338         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15339             SendToICS(ics_prefix);
15340             SendToICS("clearboard\n");
15341         } else {
15342             int nonEmpty = 0;
15343             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15344                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15345                 for (y = 0; y < BOARD_HEIGHT; y++) {
15346                     if (gameMode == IcsExamining) {
15347                         if (boards[currentMove][y][x] != EmptySquare) {
15348                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15349                                     AAA + x, ONE + y);
15350                             SendToICS(buf);
15351                         }
15352                     } else if(boards[0][y][x] != DarkSquare) {
15353                         if(boards[0][y][x] != p) nonEmpty++;
15354                         boards[0][y][x] = p;
15355                     }
15356                 }
15357             }
15358             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15359                 int r;
15360                 for(r = 0; r < BOARD_HEIGHT; r++) {
15361                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15362                     ChessSquare p = menuBoard[r][x];
15363                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15364                   }
15365                 }
15366                 DisplayMessage("Clicking clock again restores position", "");
15367                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15368                 if(!nonEmpty) { // asked to clear an empty board
15369                     CopyBoard(boards[0], menuBoard);
15370                 } else
15371                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15372                     CopyBoard(boards[0], initialPosition);
15373                 } else
15374                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15375                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15376                     CopyBoard(boards[0], erasedBoard);
15377                 } else
15378                     CopyBoard(erasedBoard, currentBoard);
15379
15380             }
15381         }
15382         if (gameMode == EditPosition) {
15383             DrawPosition(FALSE, boards[0]);
15384         }
15385         break;
15386
15387       case WhitePlay:
15388         SetWhiteToPlayEvent();
15389         break;
15390
15391       case BlackPlay:
15392         SetBlackToPlayEvent();
15393         break;
15394
15395       case EmptySquare:
15396         if (gameMode == IcsExamining) {
15397             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15398             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15399             SendToICS(buf);
15400         } else {
15401             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15402                 if(x == BOARD_LEFT-2) {
15403                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15404                     boards[0][y][1] = 0;
15405                 } else
15406                 if(x == BOARD_RGHT+1) {
15407                     if(y >= gameInfo.holdingsSize) break;
15408                     boards[0][y][BOARD_WIDTH-2] = 0;
15409                 } else break;
15410             }
15411             boards[0][y][x] = EmptySquare;
15412             DrawPosition(FALSE, boards[0]);
15413         }
15414         break;
15415
15416       case PromotePiece:
15417         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15418            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15419             selection = (ChessSquare) (PROMOTED(piece));
15420         } else if(piece == EmptySquare) selection = WhiteSilver;
15421         else selection = (ChessSquare)((int)piece - 1);
15422         goto defaultlabel;
15423
15424       case DemotePiece:
15425         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15426            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15427             selection = (ChessSquare) (DEMOTED(piece));
15428         } else if(piece == EmptySquare) selection = BlackSilver;
15429         else selection = (ChessSquare)((int)piece + 1);
15430         goto defaultlabel;
15431
15432       case WhiteQueen:
15433       case BlackQueen:
15434         if(gameInfo.variant == VariantShatranj ||
15435            gameInfo.variant == VariantXiangqi  ||
15436            gameInfo.variant == VariantCourier  ||
15437            gameInfo.variant == VariantASEAN    ||
15438            gameInfo.variant == VariantMakruk     )
15439             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15440         goto defaultlabel;
15441
15442       case WhiteKing:
15443       case BlackKing:
15444         if(gameInfo.variant == VariantXiangqi)
15445             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15446         if(gameInfo.variant == VariantKnightmate)
15447             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15448       default:
15449         defaultlabel:
15450         if (gameMode == IcsExamining) {
15451             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15452             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15453                      PieceToChar(selection), AAA + x, ONE + y);
15454             SendToICS(buf);
15455         } else {
15456             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15457                 int n;
15458                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15459                     n = PieceToNumber(selection - BlackPawn);
15460                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15461                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15462                     boards[0][BOARD_HEIGHT-1-n][1]++;
15463                 } else
15464                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15465                     n = PieceToNumber(selection);
15466                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15467                     boards[0][n][BOARD_WIDTH-1] = selection;
15468                     boards[0][n][BOARD_WIDTH-2]++;
15469                 }
15470             } else
15471             boards[0][y][x] = selection;
15472             DrawPosition(TRUE, boards[0]);
15473             ClearHighlights();
15474             fromX = fromY = -1;
15475         }
15476         break;
15477     }
15478 }
15479
15480
15481 void
15482 DropMenuEvent (ChessSquare selection, int x, int y)
15483 {
15484     ChessMove moveType;
15485
15486     switch (gameMode) {
15487       case IcsPlayingWhite:
15488       case MachinePlaysBlack:
15489         if (!WhiteOnMove(currentMove)) {
15490             DisplayMoveError(_("It is Black's turn"));
15491             return;
15492         }
15493         moveType = WhiteDrop;
15494         break;
15495       case IcsPlayingBlack:
15496       case MachinePlaysWhite:
15497         if (WhiteOnMove(currentMove)) {
15498             DisplayMoveError(_("It is White's turn"));
15499             return;
15500         }
15501         moveType = BlackDrop;
15502         break;
15503       case EditGame:
15504         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15505         break;
15506       default:
15507         return;
15508     }
15509
15510     if (moveType == BlackDrop && selection < BlackPawn) {
15511       selection = (ChessSquare) ((int) selection
15512                                  + (int) BlackPawn - (int) WhitePawn);
15513     }
15514     if (boards[currentMove][y][x] != EmptySquare) {
15515         DisplayMoveError(_("That square is occupied"));
15516         return;
15517     }
15518
15519     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15520 }
15521
15522 void
15523 AcceptEvent ()
15524 {
15525     /* Accept a pending offer of any kind from opponent */
15526
15527     if (appData.icsActive) {
15528         SendToICS(ics_prefix);
15529         SendToICS("accept\n");
15530     } else if (cmailMsgLoaded) {
15531         if (currentMove == cmailOldMove &&
15532             commentList[cmailOldMove] != NULL &&
15533             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15534                    "Black offers a draw" : "White offers a draw")) {
15535             TruncateGame();
15536             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15537             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15538         } else {
15539             DisplayError(_("There is no pending offer on this move"), 0);
15540             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15541         }
15542     } else {
15543         /* Not used for offers from chess program */
15544     }
15545 }
15546
15547 void
15548 DeclineEvent ()
15549 {
15550     /* Decline a pending offer of any kind from opponent */
15551
15552     if (appData.icsActive) {
15553         SendToICS(ics_prefix);
15554         SendToICS("decline\n");
15555     } else if (cmailMsgLoaded) {
15556         if (currentMove == cmailOldMove &&
15557             commentList[cmailOldMove] != NULL &&
15558             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15559                    "Black offers a draw" : "White offers a draw")) {
15560 #ifdef NOTDEF
15561             AppendComment(cmailOldMove, "Draw declined", TRUE);
15562             DisplayComment(cmailOldMove - 1, "Draw declined");
15563 #endif /*NOTDEF*/
15564         } else {
15565             DisplayError(_("There is no pending offer on this move"), 0);
15566         }
15567     } else {
15568         /* Not used for offers from chess program */
15569     }
15570 }
15571
15572 void
15573 RematchEvent ()
15574 {
15575     /* Issue ICS rematch command */
15576     if (appData.icsActive) {
15577         SendToICS(ics_prefix);
15578         SendToICS("rematch\n");
15579     }
15580 }
15581
15582 void
15583 CallFlagEvent ()
15584 {
15585     /* Call your opponent's flag (claim a win on time) */
15586     if (appData.icsActive) {
15587         SendToICS(ics_prefix);
15588         SendToICS("flag\n");
15589     } else {
15590         switch (gameMode) {
15591           default:
15592             return;
15593           case MachinePlaysWhite:
15594             if (whiteFlag) {
15595                 if (blackFlag)
15596                   GameEnds(GameIsDrawn, "Both players ran out of time",
15597                            GE_PLAYER);
15598                 else
15599                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15600             } else {
15601                 DisplayError(_("Your opponent is not out of time"), 0);
15602             }
15603             break;
15604           case MachinePlaysBlack:
15605             if (blackFlag) {
15606                 if (whiteFlag)
15607                   GameEnds(GameIsDrawn, "Both players ran out of time",
15608                            GE_PLAYER);
15609                 else
15610                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15611             } else {
15612                 DisplayError(_("Your opponent is not out of time"), 0);
15613             }
15614             break;
15615         }
15616     }
15617 }
15618
15619 void
15620 ClockClick (int which)
15621 {       // [HGM] code moved to back-end from winboard.c
15622         if(which) { // black clock
15623           if (gameMode == EditPosition || gameMode == IcsExamining) {
15624             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15625             SetBlackToPlayEvent();
15626           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15627                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15628           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15629           } else if (shiftKey) {
15630             AdjustClock(which, -1);
15631           } else if (gameMode == IcsPlayingWhite ||
15632                      gameMode == MachinePlaysBlack) {
15633             CallFlagEvent();
15634           }
15635         } else { // white clock
15636           if (gameMode == EditPosition || gameMode == IcsExamining) {
15637             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15638             SetWhiteToPlayEvent();
15639           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15640                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15641           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15642           } else if (shiftKey) {
15643             AdjustClock(which, -1);
15644           } else if (gameMode == IcsPlayingBlack ||
15645                    gameMode == MachinePlaysWhite) {
15646             CallFlagEvent();
15647           }
15648         }
15649 }
15650
15651 void
15652 DrawEvent ()
15653 {
15654     /* Offer draw or accept pending draw offer from opponent */
15655
15656     if (appData.icsActive) {
15657         /* Note: tournament rules require draw offers to be
15658            made after you make your move but before you punch
15659            your clock.  Currently ICS doesn't let you do that;
15660            instead, you immediately punch your clock after making
15661            a move, but you can offer a draw at any time. */
15662
15663         SendToICS(ics_prefix);
15664         SendToICS("draw\n");
15665         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15666     } else if (cmailMsgLoaded) {
15667         if (currentMove == cmailOldMove &&
15668             commentList[cmailOldMove] != NULL &&
15669             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15670                    "Black offers a draw" : "White offers a draw")) {
15671             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15672             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15673         } else if (currentMove == cmailOldMove + 1) {
15674             char *offer = WhiteOnMove(cmailOldMove) ?
15675               "White offers a draw" : "Black offers a draw";
15676             AppendComment(currentMove, offer, TRUE);
15677             DisplayComment(currentMove - 1, offer);
15678             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15679         } else {
15680             DisplayError(_("You must make your move before offering a draw"), 0);
15681             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15682         }
15683     } else if (first.offeredDraw) {
15684         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15685     } else {
15686         if (first.sendDrawOffers) {
15687             SendToProgram("draw\n", &first);
15688             userOfferedDraw = TRUE;
15689         }
15690     }
15691 }
15692
15693 void
15694 AdjournEvent ()
15695 {
15696     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15697
15698     if (appData.icsActive) {
15699         SendToICS(ics_prefix);
15700         SendToICS("adjourn\n");
15701     } else {
15702         /* Currently GNU Chess doesn't offer or accept Adjourns */
15703     }
15704 }
15705
15706
15707 void
15708 AbortEvent ()
15709 {
15710     /* Offer Abort or accept pending Abort offer from opponent */
15711
15712     if (appData.icsActive) {
15713         SendToICS(ics_prefix);
15714         SendToICS("abort\n");
15715     } else {
15716         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15717     }
15718 }
15719
15720 void
15721 ResignEvent ()
15722 {
15723     /* Resign.  You can do this even if it's not your turn. */
15724
15725     if (appData.icsActive) {
15726         SendToICS(ics_prefix);
15727         SendToICS("resign\n");
15728     } else {
15729         switch (gameMode) {
15730           case MachinePlaysWhite:
15731             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15732             break;
15733           case MachinePlaysBlack:
15734             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15735             break;
15736           case EditGame:
15737             if (cmailMsgLoaded) {
15738                 TruncateGame();
15739                 if (WhiteOnMove(cmailOldMove)) {
15740                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15741                 } else {
15742                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15743                 }
15744                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15745             }
15746             break;
15747           default:
15748             break;
15749         }
15750     }
15751 }
15752
15753
15754 void
15755 StopObservingEvent ()
15756 {
15757     /* Stop observing current games */
15758     SendToICS(ics_prefix);
15759     SendToICS("unobserve\n");
15760 }
15761
15762 void
15763 StopExaminingEvent ()
15764 {
15765     /* Stop observing current game */
15766     SendToICS(ics_prefix);
15767     SendToICS("unexamine\n");
15768 }
15769
15770 void
15771 ForwardInner (int target)
15772 {
15773     int limit; int oldSeekGraphUp = seekGraphUp;
15774
15775     if (appData.debugMode)
15776         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15777                 target, currentMove, forwardMostMove);
15778
15779     if (gameMode == EditPosition)
15780       return;
15781
15782     seekGraphUp = FALSE;
15783     MarkTargetSquares(1);
15784     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15785
15786     if (gameMode == PlayFromGameFile && !pausing)
15787       PauseEvent();
15788
15789     if (gameMode == IcsExamining && pausing)
15790       limit = pauseExamForwardMostMove;
15791     else
15792       limit = forwardMostMove;
15793
15794     if (target > limit) target = limit;
15795
15796     if (target > 0 && moveList[target - 1][0]) {
15797         int fromX, fromY, toX, toY;
15798         toX = moveList[target - 1][2] - AAA;
15799         toY = moveList[target - 1][3] - ONE;
15800         if (moveList[target - 1][1] == '@') {
15801             if (appData.highlightLastMove) {
15802                 SetHighlights(-1, -1, toX, toY);
15803             }
15804         } else {
15805             int viaX = moveList[target - 1][5] - AAA;
15806             int viaY = moveList[target - 1][6] - ONE;
15807             fromX = moveList[target - 1][0] - AAA;
15808             fromY = moveList[target - 1][1] - ONE;
15809             if (target == currentMove + 1) {
15810                 if(moveList[target - 1][4] == ';') { // multi-leg
15811                     ChessSquare piece = boards[currentMove][viaY][viaX];
15812                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15813                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15814                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15815                     boards[currentMove][viaY][viaX] = piece;
15816                 } else
15817                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15818             }
15819             if (appData.highlightLastMove) {
15820                 SetHighlights(fromX, fromY, toX, toY);
15821             }
15822         }
15823     }
15824     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15825         gameMode == Training || gameMode == PlayFromGameFile ||
15826         gameMode == AnalyzeFile) {
15827         while (currentMove < target) {
15828             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15829             SendMoveToProgram(currentMove++, &first);
15830         }
15831     } else {
15832         currentMove = target;
15833     }
15834
15835     if (gameMode == EditGame || gameMode == EndOfGame) {
15836         whiteTimeRemaining = timeRemaining[0][currentMove];
15837         blackTimeRemaining = timeRemaining[1][currentMove];
15838     }
15839     DisplayBothClocks();
15840     DisplayMove(currentMove - 1);
15841     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15842     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15843     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15844         DisplayComment(currentMove - 1, commentList[currentMove]);
15845     }
15846     ClearMap(); // [HGM] exclude: invalidate map
15847 }
15848
15849
15850 void
15851 ForwardEvent ()
15852 {
15853     if (gameMode == IcsExamining && !pausing) {
15854         SendToICS(ics_prefix);
15855         SendToICS("forward\n");
15856     } else {
15857         ForwardInner(currentMove + 1);
15858     }
15859 }
15860
15861 void
15862 ToEndEvent ()
15863 {
15864     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15865         /* to optimze, we temporarily turn off analysis mode while we feed
15866          * the remaining moves to the engine. Otherwise we get analysis output
15867          * after each move.
15868          */
15869         if (first.analysisSupport) {
15870           SendToProgram("exit\nforce\n", &first);
15871           first.analyzing = FALSE;
15872         }
15873     }
15874
15875     if (gameMode == IcsExamining && !pausing) {
15876         SendToICS(ics_prefix);
15877         SendToICS("forward 999999\n");
15878     } else {
15879         ForwardInner(forwardMostMove);
15880     }
15881
15882     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15883         /* we have fed all the moves, so reactivate analysis mode */
15884         SendToProgram("analyze\n", &first);
15885         first.analyzing = TRUE;
15886         /*first.maybeThinking = TRUE;*/
15887         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15888     }
15889 }
15890
15891 void
15892 BackwardInner (int target)
15893 {
15894     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15895
15896     if (appData.debugMode)
15897         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15898                 target, currentMove, forwardMostMove);
15899
15900     if (gameMode == EditPosition) return;
15901     seekGraphUp = FALSE;
15902     MarkTargetSquares(1);
15903     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15904     if (currentMove <= backwardMostMove) {
15905         ClearHighlights();
15906         DrawPosition(full_redraw, boards[currentMove]);
15907         return;
15908     }
15909     if (gameMode == PlayFromGameFile && !pausing)
15910       PauseEvent();
15911
15912     if (moveList[target][0]) {
15913         int fromX, fromY, toX, toY;
15914         toX = moveList[target][2] - AAA;
15915         toY = moveList[target][3] - ONE;
15916         if (moveList[target][1] == '@') {
15917             if (appData.highlightLastMove) {
15918                 SetHighlights(-1, -1, toX, toY);
15919             }
15920         } else {
15921             fromX = moveList[target][0] - AAA;
15922             fromY = moveList[target][1] - ONE;
15923             if (target == currentMove - 1) {
15924                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15925             }
15926             if (appData.highlightLastMove) {
15927                 SetHighlights(fromX, fromY, toX, toY);
15928             }
15929         }
15930     }
15931     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15932         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15933         while (currentMove > target) {
15934             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15935                 // null move cannot be undone. Reload program with move history before it.
15936                 int i;
15937                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15938                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15939                 }
15940                 SendBoard(&first, i);
15941               if(second.analyzing) SendBoard(&second, i);
15942                 for(currentMove=i; currentMove<target; currentMove++) {
15943                     SendMoveToProgram(currentMove, &first);
15944                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15945                 }
15946                 break;
15947             }
15948             SendToBoth("undo\n");
15949             currentMove--;
15950         }
15951     } else {
15952         currentMove = target;
15953     }
15954
15955     if (gameMode == EditGame || gameMode == EndOfGame) {
15956         whiteTimeRemaining = timeRemaining[0][currentMove];
15957         blackTimeRemaining = timeRemaining[1][currentMove];
15958     }
15959     DisplayBothClocks();
15960     DisplayMove(currentMove - 1);
15961     DrawPosition(full_redraw, boards[currentMove]);
15962     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15963     // [HGM] PV info: routine tests if comment empty
15964     DisplayComment(currentMove - 1, commentList[currentMove]);
15965     ClearMap(); // [HGM] exclude: invalidate map
15966 }
15967
15968 void
15969 BackwardEvent ()
15970 {
15971     if (gameMode == IcsExamining && !pausing) {
15972         SendToICS(ics_prefix);
15973         SendToICS("backward\n");
15974     } else {
15975         BackwardInner(currentMove - 1);
15976     }
15977 }
15978
15979 void
15980 ToStartEvent ()
15981 {
15982     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15983         /* to optimize, we temporarily turn off analysis mode while we undo
15984          * all the moves. Otherwise we get analysis output after each undo.
15985          */
15986         if (first.analysisSupport) {
15987           SendToProgram("exit\nforce\n", &first);
15988           first.analyzing = FALSE;
15989         }
15990     }
15991
15992     if (gameMode == IcsExamining && !pausing) {
15993         SendToICS(ics_prefix);
15994         SendToICS("backward 999999\n");
15995     } else {
15996         BackwardInner(backwardMostMove);
15997     }
15998
15999     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16000         /* we have fed all the moves, so reactivate analysis mode */
16001         SendToProgram("analyze\n", &first);
16002         first.analyzing = TRUE;
16003         /*first.maybeThinking = TRUE;*/
16004         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16005     }
16006 }
16007
16008 void
16009 ToNrEvent (int to)
16010 {
16011   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16012   if (to >= forwardMostMove) to = forwardMostMove;
16013   if (to <= backwardMostMove) to = backwardMostMove;
16014   if (to < currentMove) {
16015     BackwardInner(to);
16016   } else {
16017     ForwardInner(to);
16018   }
16019 }
16020
16021 void
16022 RevertEvent (Boolean annotate)
16023 {
16024     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16025         return;
16026     }
16027     if (gameMode != IcsExamining) {
16028         DisplayError(_("You are not examining a game"), 0);
16029         return;
16030     }
16031     if (pausing) {
16032         DisplayError(_("You can't revert while pausing"), 0);
16033         return;
16034     }
16035     SendToICS(ics_prefix);
16036     SendToICS("revert\n");
16037 }
16038
16039 void
16040 RetractMoveEvent ()
16041 {
16042     switch (gameMode) {
16043       case MachinePlaysWhite:
16044       case MachinePlaysBlack:
16045         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16046             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16047             return;
16048         }
16049         if (forwardMostMove < 2) return;
16050         currentMove = forwardMostMove = forwardMostMove - 2;
16051         whiteTimeRemaining = timeRemaining[0][currentMove];
16052         blackTimeRemaining = timeRemaining[1][currentMove];
16053         DisplayBothClocks();
16054         DisplayMove(currentMove - 1);
16055         ClearHighlights();/*!! could figure this out*/
16056         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16057         SendToProgram("remove\n", &first);
16058         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16059         break;
16060
16061       case BeginningOfGame:
16062       default:
16063         break;
16064
16065       case IcsPlayingWhite:
16066       case IcsPlayingBlack:
16067         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16068             SendToICS(ics_prefix);
16069             SendToICS("takeback 2\n");
16070         } else {
16071             SendToICS(ics_prefix);
16072             SendToICS("takeback 1\n");
16073         }
16074         break;
16075     }
16076 }
16077
16078 void
16079 MoveNowEvent ()
16080 {
16081     ChessProgramState *cps;
16082
16083     switch (gameMode) {
16084       case MachinePlaysWhite:
16085         if (!WhiteOnMove(forwardMostMove)) {
16086             DisplayError(_("It is your turn"), 0);
16087             return;
16088         }
16089         cps = &first;
16090         break;
16091       case MachinePlaysBlack:
16092         if (WhiteOnMove(forwardMostMove)) {
16093             DisplayError(_("It is your turn"), 0);
16094             return;
16095         }
16096         cps = &first;
16097         break;
16098       case TwoMachinesPlay:
16099         if (WhiteOnMove(forwardMostMove) ==
16100             (first.twoMachinesColor[0] == 'w')) {
16101             cps = &first;
16102         } else {
16103             cps = &second;
16104         }
16105         break;
16106       case BeginningOfGame:
16107       default:
16108         return;
16109     }
16110     SendToProgram("?\n", cps);
16111 }
16112
16113 void
16114 TruncateGameEvent ()
16115 {
16116     EditGameEvent();
16117     if (gameMode != EditGame) return;
16118     TruncateGame();
16119 }
16120
16121 void
16122 TruncateGame ()
16123 {
16124     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16125     if (forwardMostMove > currentMove) {
16126         if (gameInfo.resultDetails != NULL) {
16127             free(gameInfo.resultDetails);
16128             gameInfo.resultDetails = NULL;
16129             gameInfo.result = GameUnfinished;
16130         }
16131         forwardMostMove = currentMove;
16132         HistorySet(parseList, backwardMostMove, forwardMostMove,
16133                    currentMove-1);
16134     }
16135 }
16136
16137 void
16138 HintEvent ()
16139 {
16140     if (appData.noChessProgram) return;
16141     switch (gameMode) {
16142       case MachinePlaysWhite:
16143         if (WhiteOnMove(forwardMostMove)) {
16144             DisplayError(_("Wait until your turn."), 0);
16145             return;
16146         }
16147         break;
16148       case BeginningOfGame:
16149       case MachinePlaysBlack:
16150         if (!WhiteOnMove(forwardMostMove)) {
16151             DisplayError(_("Wait until your turn."), 0);
16152             return;
16153         }
16154         break;
16155       default:
16156         DisplayError(_("No hint available"), 0);
16157         return;
16158     }
16159     SendToProgram("hint\n", &first);
16160     hintRequested = TRUE;
16161 }
16162
16163 int
16164 SaveSelected (FILE *g, int dummy, char *dummy2)
16165 {
16166     ListGame * lg = (ListGame *) gameList.head;
16167     int nItem, cnt=0;
16168     FILE *f;
16169
16170     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16171         DisplayError(_("Game list not loaded or empty"), 0);
16172         return 0;
16173     }
16174
16175     creatingBook = TRUE; // suppresses stuff during load game
16176
16177     /* Get list size */
16178     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16179         if(lg->position >= 0) { // selected?
16180             LoadGame(f, nItem, "", TRUE);
16181             SaveGamePGN2(g); // leaves g open
16182             cnt++; DoEvents();
16183         }
16184         lg = (ListGame *) lg->node.succ;
16185     }
16186
16187     fclose(g);
16188     creatingBook = FALSE;
16189
16190     return cnt;
16191 }
16192
16193 void
16194 CreateBookEvent ()
16195 {
16196     ListGame * lg = (ListGame *) gameList.head;
16197     FILE *f, *g;
16198     int nItem;
16199     static int secondTime = FALSE;
16200
16201     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16202         DisplayError(_("Game list not loaded or empty"), 0);
16203         return;
16204     }
16205
16206     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16207         fclose(g);
16208         secondTime++;
16209         DisplayNote(_("Book file exists! Try again for overwrite."));
16210         return;
16211     }
16212
16213     creatingBook = TRUE;
16214     secondTime = FALSE;
16215
16216     /* Get list size */
16217     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16218         if(lg->position >= 0) {
16219             LoadGame(f, nItem, "", TRUE);
16220             AddGameToBook(TRUE);
16221             DoEvents();
16222         }
16223         lg = (ListGame *) lg->node.succ;
16224     }
16225
16226     creatingBook = FALSE;
16227     FlushBook();
16228 }
16229
16230 void
16231 BookEvent ()
16232 {
16233     if (appData.noChessProgram) return;
16234     switch (gameMode) {
16235       case MachinePlaysWhite:
16236         if (WhiteOnMove(forwardMostMove)) {
16237             DisplayError(_("Wait until your turn."), 0);
16238             return;
16239         }
16240         break;
16241       case BeginningOfGame:
16242       case MachinePlaysBlack:
16243         if (!WhiteOnMove(forwardMostMove)) {
16244             DisplayError(_("Wait until your turn."), 0);
16245             return;
16246         }
16247         break;
16248       case EditPosition:
16249         EditPositionDone(TRUE);
16250         break;
16251       case TwoMachinesPlay:
16252         return;
16253       default:
16254         break;
16255     }
16256     SendToProgram("bk\n", &first);
16257     bookOutput[0] = NULLCHAR;
16258     bookRequested = TRUE;
16259 }
16260
16261 void
16262 AboutGameEvent ()
16263 {
16264     char *tags = PGNTags(&gameInfo);
16265     TagsPopUp(tags, CmailMsg());
16266     free(tags);
16267 }
16268
16269 /* end button procedures */
16270
16271 void
16272 PrintPosition (FILE *fp, int move)
16273 {
16274     int i, j;
16275
16276     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16277         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16278             char c = PieceToChar(boards[move][i][j]);
16279             fputc(c == 'x' ? '.' : c, fp);
16280             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16281         }
16282     }
16283     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16284       fprintf(fp, "white to play\n");
16285     else
16286       fprintf(fp, "black to play\n");
16287 }
16288
16289 void
16290 PrintOpponents (FILE *fp)
16291 {
16292     if (gameInfo.white != NULL) {
16293         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16294     } else {
16295         fprintf(fp, "\n");
16296     }
16297 }
16298
16299 /* Find last component of program's own name, using some heuristics */
16300 void
16301 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16302 {
16303     char *p, *q, c;
16304     int local = (strcmp(host, "localhost") == 0);
16305     while (!local && (p = strchr(prog, ';')) != NULL) {
16306         p++;
16307         while (*p == ' ') p++;
16308         prog = p;
16309     }
16310     if (*prog == '"' || *prog == '\'') {
16311         q = strchr(prog + 1, *prog);
16312     } else {
16313         q = strchr(prog, ' ');
16314     }
16315     if (q == NULL) q = prog + strlen(prog);
16316     p = q;
16317     while (p >= prog && *p != '/' && *p != '\\') p--;
16318     p++;
16319     if(p == prog && *p == '"') p++;
16320     c = *q; *q = 0;
16321     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16322     memcpy(buf, p, q - p);
16323     buf[q - p] = NULLCHAR;
16324     if (!local) {
16325         strcat(buf, "@");
16326         strcat(buf, host);
16327     }
16328 }
16329
16330 char *
16331 TimeControlTagValue ()
16332 {
16333     char buf[MSG_SIZ];
16334     if (!appData.clockMode) {
16335       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16336     } else if (movesPerSession > 0) {
16337       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16338     } else if (timeIncrement == 0) {
16339       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16340     } else {
16341       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16342     }
16343     return StrSave(buf);
16344 }
16345
16346 void
16347 SetGameInfo ()
16348 {
16349     /* This routine is used only for certain modes */
16350     VariantClass v = gameInfo.variant;
16351     ChessMove r = GameUnfinished;
16352     char *p = NULL;
16353
16354     if(keepInfo) return;
16355
16356     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16357         r = gameInfo.result;
16358         p = gameInfo.resultDetails;
16359         gameInfo.resultDetails = NULL;
16360     }
16361     ClearGameInfo(&gameInfo);
16362     gameInfo.variant = v;
16363
16364     switch (gameMode) {
16365       case MachinePlaysWhite:
16366         gameInfo.event = StrSave( appData.pgnEventHeader );
16367         gameInfo.site = StrSave(HostName());
16368         gameInfo.date = PGNDate();
16369         gameInfo.round = StrSave("-");
16370         gameInfo.white = StrSave(first.tidy);
16371         gameInfo.black = StrSave(UserName());
16372         gameInfo.timeControl = TimeControlTagValue();
16373         break;
16374
16375       case MachinePlaysBlack:
16376         gameInfo.event = StrSave( appData.pgnEventHeader );
16377         gameInfo.site = StrSave(HostName());
16378         gameInfo.date = PGNDate();
16379         gameInfo.round = StrSave("-");
16380         gameInfo.white = StrSave(UserName());
16381         gameInfo.black = StrSave(first.tidy);
16382         gameInfo.timeControl = TimeControlTagValue();
16383         break;
16384
16385       case TwoMachinesPlay:
16386         gameInfo.event = StrSave( appData.pgnEventHeader );
16387         gameInfo.site = StrSave(HostName());
16388         gameInfo.date = PGNDate();
16389         if (roundNr > 0) {
16390             char buf[MSG_SIZ];
16391             snprintf(buf, MSG_SIZ, "%d", roundNr);
16392             gameInfo.round = StrSave(buf);
16393         } else {
16394             gameInfo.round = StrSave("-");
16395         }
16396         if (first.twoMachinesColor[0] == 'w') {
16397             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16398             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16399         } else {
16400             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16401             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16402         }
16403         gameInfo.timeControl = TimeControlTagValue();
16404         break;
16405
16406       case EditGame:
16407         gameInfo.event = StrSave("Edited game");
16408         gameInfo.site = StrSave(HostName());
16409         gameInfo.date = PGNDate();
16410         gameInfo.round = StrSave("-");
16411         gameInfo.white = StrSave("-");
16412         gameInfo.black = StrSave("-");
16413         gameInfo.result = r;
16414         gameInfo.resultDetails = p;
16415         break;
16416
16417       case EditPosition:
16418         gameInfo.event = StrSave("Edited position");
16419         gameInfo.site = StrSave(HostName());
16420         gameInfo.date = PGNDate();
16421         gameInfo.round = StrSave("-");
16422         gameInfo.white = StrSave("-");
16423         gameInfo.black = StrSave("-");
16424         break;
16425
16426       case IcsPlayingWhite:
16427       case IcsPlayingBlack:
16428       case IcsObserving:
16429       case IcsExamining:
16430         break;
16431
16432       case PlayFromGameFile:
16433         gameInfo.event = StrSave("Game from non-PGN file");
16434         gameInfo.site = StrSave(HostName());
16435         gameInfo.date = PGNDate();
16436         gameInfo.round = StrSave("-");
16437         gameInfo.white = StrSave("?");
16438         gameInfo.black = StrSave("?");
16439         break;
16440
16441       default:
16442         break;
16443     }
16444 }
16445
16446 void
16447 ReplaceComment (int index, char *text)
16448 {
16449     int len;
16450     char *p;
16451     float score;
16452
16453     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16454        pvInfoList[index-1].depth == len &&
16455        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16456        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16457     while (*text == '\n') text++;
16458     len = strlen(text);
16459     while (len > 0 && text[len - 1] == '\n') len--;
16460
16461     if (commentList[index] != NULL)
16462       free(commentList[index]);
16463
16464     if (len == 0) {
16465         commentList[index] = NULL;
16466         return;
16467     }
16468   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16469       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16470       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16471     commentList[index] = (char *) malloc(len + 2);
16472     strncpy(commentList[index], text, len);
16473     commentList[index][len] = '\n';
16474     commentList[index][len + 1] = NULLCHAR;
16475   } else {
16476     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16477     char *p;
16478     commentList[index] = (char *) malloc(len + 7);
16479     safeStrCpy(commentList[index], "{\n", 3);
16480     safeStrCpy(commentList[index]+2, text, len+1);
16481     commentList[index][len+2] = NULLCHAR;
16482     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16483     strcat(commentList[index], "\n}\n");
16484   }
16485 }
16486
16487 void
16488 CrushCRs (char *text)
16489 {
16490   char *p = text;
16491   char *q = text;
16492   char ch;
16493
16494   do {
16495     ch = *p++;
16496     if (ch == '\r') continue;
16497     *q++ = ch;
16498   } while (ch != '\0');
16499 }
16500
16501 void
16502 AppendComment (int index, char *text, Boolean addBraces)
16503 /* addBraces  tells if we should add {} */
16504 {
16505     int oldlen, len;
16506     char *old;
16507
16508 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16509     if(addBraces == 3) addBraces = 0; else // force appending literally
16510     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16511
16512     CrushCRs(text);
16513     while (*text == '\n') text++;
16514     len = strlen(text);
16515     while (len > 0 && text[len - 1] == '\n') len--;
16516     text[len] = NULLCHAR;
16517
16518     if (len == 0) return;
16519
16520     if (commentList[index] != NULL) {
16521       Boolean addClosingBrace = addBraces;
16522         old = commentList[index];
16523         oldlen = strlen(old);
16524         while(commentList[index][oldlen-1] ==  '\n')
16525           commentList[index][--oldlen] = NULLCHAR;
16526         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16527         safeStrCpy(commentList[index], old, oldlen + len + 6);
16528         free(old);
16529         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16530         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16531           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16532           while (*text == '\n') { text++; len--; }
16533           commentList[index][--oldlen] = NULLCHAR;
16534       }
16535         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16536         else          strcat(commentList[index], "\n");
16537         strcat(commentList[index], text);
16538         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16539         else          strcat(commentList[index], "\n");
16540     } else {
16541         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16542         if(addBraces)
16543           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16544         else commentList[index][0] = NULLCHAR;
16545         strcat(commentList[index], text);
16546         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16547         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16548     }
16549 }
16550
16551 static char *
16552 FindStr (char * text, char * sub_text)
16553 {
16554     char * result = strstr( text, sub_text );
16555
16556     if( result != NULL ) {
16557         result += strlen( sub_text );
16558     }
16559
16560     return result;
16561 }
16562
16563 /* [AS] Try to extract PV info from PGN comment */
16564 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16565 char *
16566 GetInfoFromComment (int index, char * text)
16567 {
16568     char * sep = text, *p;
16569
16570     if( text != NULL && index > 0 ) {
16571         int score = 0;
16572         int depth = 0;
16573         int time = -1, sec = 0, deci;
16574         char * s_eval = FindStr( text, "[%eval " );
16575         char * s_emt = FindStr( text, "[%emt " );
16576 #if 0
16577         if( s_eval != NULL || s_emt != NULL ) {
16578 #else
16579         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16580 #endif
16581             /* New style */
16582             char delim;
16583
16584             if( s_eval != NULL ) {
16585                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16586                     return text;
16587                 }
16588
16589                 if( delim != ']' ) {
16590                     return text;
16591                 }
16592             }
16593
16594             if( s_emt != NULL ) {
16595             }
16596                 return text;
16597         }
16598         else {
16599             /* We expect something like: [+|-]nnn.nn/dd */
16600             int score_lo = 0;
16601
16602             if(*text != '{') return text; // [HGM] braces: must be normal comment
16603
16604             sep = strchr( text, '/' );
16605             if( sep == NULL || sep < (text+4) ) {
16606                 return text;
16607             }
16608
16609             p = text;
16610             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16611             if(p[1] == '(') { // comment starts with PV
16612                p = strchr(p, ')'); // locate end of PV
16613                if(p == NULL || sep < p+5) return text;
16614                // at this point we have something like "{(.*) +0.23/6 ..."
16615                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16616                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16617                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16618             }
16619             time = -1; sec = -1; deci = -1;
16620             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16621                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16622                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16623                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16624                 return text;
16625             }
16626
16627             if( score_lo < 0 || score_lo >= 100 ) {
16628                 return text;
16629             }
16630
16631             if(sec >= 0) time = 600*time + 10*sec; else
16632             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16633
16634             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16635
16636             /* [HGM] PV time: now locate end of PV info */
16637             while( *++sep >= '0' && *sep <= '9'); // strip depth
16638             if(time >= 0)
16639             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16640             if(sec >= 0)
16641             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16642             if(deci >= 0)
16643             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16644             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16645         }
16646
16647         if( depth <= 0 ) {
16648             return text;
16649         }
16650
16651         if( time < 0 ) {
16652             time = -1;
16653         }
16654
16655         pvInfoList[index-1].depth = depth;
16656         pvInfoList[index-1].score = score;
16657         pvInfoList[index-1].time  = 10*time; // centi-sec
16658         if(*sep == '}') *sep = 0; else *--sep = '{';
16659         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16660     }
16661     return sep;
16662 }
16663
16664 void
16665 SendToProgram (char *message, ChessProgramState *cps)
16666 {
16667     int count, outCount, error;
16668     char buf[MSG_SIZ];
16669
16670     if (cps->pr == NoProc) return;
16671     Attention(cps);
16672
16673     if (appData.debugMode) {
16674         TimeMark now;
16675         GetTimeMark(&now);
16676         fprintf(debugFP, "%ld >%-6s: %s",
16677                 SubtractTimeMarks(&now, &programStartTime),
16678                 cps->which, message);
16679         if(serverFP)
16680             fprintf(serverFP, "%ld >%-6s: %s",
16681                 SubtractTimeMarks(&now, &programStartTime),
16682                 cps->which, message), fflush(serverFP);
16683     }
16684
16685     count = strlen(message);
16686     outCount = OutputToProcess(cps->pr, message, count, &error);
16687     if (outCount < count && !exiting
16688                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16689       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16690       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16691         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16692             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16693                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16694                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16695                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16696             } else {
16697                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16698                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16699                 gameInfo.result = res;
16700             }
16701             gameInfo.resultDetails = StrSave(buf);
16702         }
16703         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16704         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16705     }
16706 }
16707
16708 void
16709 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16710 {
16711     char *end_str;
16712     char buf[MSG_SIZ];
16713     ChessProgramState *cps = (ChessProgramState *)closure;
16714
16715     if (isr != cps->isr) return; /* Killed intentionally */
16716     if (count <= 0) {
16717         if (count == 0) {
16718             RemoveInputSource(cps->isr);
16719             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16720                     _(cps->which), cps->program);
16721             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16722             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16723                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16724                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16725                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16726                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16727                 } else {
16728                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16729                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16730                     gameInfo.result = res;
16731                 }
16732                 gameInfo.resultDetails = StrSave(buf);
16733             }
16734             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16735             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16736         } else {
16737             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16738                     _(cps->which), cps->program);
16739             RemoveInputSource(cps->isr);
16740
16741             /* [AS] Program is misbehaving badly... kill it */
16742             if( count == -2 ) {
16743                 DestroyChildProcess( cps->pr, 9 );
16744                 cps->pr = NoProc;
16745             }
16746
16747             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16748         }
16749         return;
16750     }
16751
16752     if ((end_str = strchr(message, '\r')) != NULL)
16753       *end_str = NULLCHAR;
16754     if ((end_str = strchr(message, '\n')) != NULL)
16755       *end_str = NULLCHAR;
16756
16757     if (appData.debugMode) {
16758         TimeMark now; int print = 1;
16759         char *quote = ""; char c; int i;
16760
16761         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16762                 char start = message[0];
16763                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16764                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16765                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16766                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16767                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16768                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16769                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16770                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16771                    sscanf(message, "hint: %c", &c)!=1 &&
16772                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16773                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16774                     print = (appData.engineComments >= 2);
16775                 }
16776                 message[0] = start; // restore original message
16777         }
16778         if(print) {
16779                 GetTimeMark(&now);
16780                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16781                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16782                         quote,
16783                         message);
16784                 if(serverFP)
16785                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16786                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16787                         quote,
16788                         message), fflush(serverFP);
16789         }
16790     }
16791
16792     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16793     if (appData.icsEngineAnalyze) {
16794         if (strstr(message, "whisper") != NULL ||
16795              strstr(message, "kibitz") != NULL ||
16796             strstr(message, "tellics") != NULL) return;
16797     }
16798
16799     HandleMachineMove(message, cps);
16800 }
16801
16802
16803 void
16804 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16805 {
16806     char buf[MSG_SIZ];
16807     int seconds;
16808
16809     if( timeControl_2 > 0 ) {
16810         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16811             tc = timeControl_2;
16812         }
16813     }
16814     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16815     inc /= cps->timeOdds;
16816     st  /= cps->timeOdds;
16817
16818     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16819
16820     if (st > 0) {
16821       /* Set exact time per move, normally using st command */
16822       if (cps->stKludge) {
16823         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16824         seconds = st % 60;
16825         if (seconds == 0) {
16826           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16827         } else {
16828           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16829         }
16830       } else {
16831         snprintf(buf, MSG_SIZ, "st %d\n", st);
16832       }
16833     } else {
16834       /* Set conventional or incremental time control, using level command */
16835       if (seconds == 0) {
16836         /* Note old gnuchess bug -- minutes:seconds used to not work.
16837            Fixed in later versions, but still avoid :seconds
16838            when seconds is 0. */
16839         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16840       } else {
16841         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16842                  seconds, inc/1000.);
16843       }
16844     }
16845     SendToProgram(buf, cps);
16846
16847     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16848     /* Orthogonally, limit search to given depth */
16849     if (sd > 0) {
16850       if (cps->sdKludge) {
16851         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16852       } else {
16853         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16854       }
16855       SendToProgram(buf, cps);
16856     }
16857
16858     if(cps->nps >= 0) { /* [HGM] nps */
16859         if(cps->supportsNPS == FALSE)
16860           cps->nps = -1; // don't use if engine explicitly says not supported!
16861         else {
16862           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16863           SendToProgram(buf, cps);
16864         }
16865     }
16866 }
16867
16868 ChessProgramState *
16869 WhitePlayer ()
16870 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16871 {
16872     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16873        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16874         return &second;
16875     return &first;
16876 }
16877
16878 void
16879 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16880 {
16881     char message[MSG_SIZ];
16882     long time, otime;
16883
16884     /* Note: this routine must be called when the clocks are stopped
16885        or when they have *just* been set or switched; otherwise
16886        it will be off by the time since the current tick started.
16887     */
16888     if (machineWhite) {
16889         time = whiteTimeRemaining / 10;
16890         otime = blackTimeRemaining / 10;
16891     } else {
16892         time = blackTimeRemaining / 10;
16893         otime = whiteTimeRemaining / 10;
16894     }
16895     /* [HGM] translate opponent's time by time-odds factor */
16896     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16897
16898     if (time <= 0) time = 1;
16899     if (otime <= 0) otime = 1;
16900
16901     snprintf(message, MSG_SIZ, "time %ld\n", time);
16902     SendToProgram(message, cps);
16903
16904     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16905     SendToProgram(message, cps);
16906 }
16907
16908 char *
16909 EngineDefinedVariant (ChessProgramState *cps, int n)
16910 {   // return name of n-th unknown variant that engine supports
16911     static char buf[MSG_SIZ];
16912     char *p, *s = cps->variants;
16913     if(!s) return NULL;
16914     do { // parse string from variants feature
16915       VariantClass v;
16916         p = strchr(s, ',');
16917         if(p) *p = NULLCHAR;
16918       v = StringToVariant(s);
16919       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16920         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16921             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16922                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16923                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16924                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16925             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16926         }
16927         if(p) *p++ = ',';
16928         if(n < 0) return buf;
16929     } while(s = p);
16930     return NULL;
16931 }
16932
16933 int
16934 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16935 {
16936   char buf[MSG_SIZ];
16937   int len = strlen(name);
16938   int val;
16939
16940   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16941     (*p) += len + 1;
16942     sscanf(*p, "%d", &val);
16943     *loc = (val != 0);
16944     while (**p && **p != ' ')
16945       (*p)++;
16946     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16947     SendToProgram(buf, cps);
16948     return TRUE;
16949   }
16950   return FALSE;
16951 }
16952
16953 int
16954 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16955 {
16956   char buf[MSG_SIZ];
16957   int len = strlen(name);
16958   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16959     (*p) += len + 1;
16960     sscanf(*p, "%d", loc);
16961     while (**p && **p != ' ') (*p)++;
16962     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16963     SendToProgram(buf, cps);
16964     return TRUE;
16965   }
16966   return FALSE;
16967 }
16968
16969 int
16970 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16971 {
16972   char buf[MSG_SIZ];
16973   int len = strlen(name);
16974   if (strncmp((*p), name, len) == 0
16975       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16976     (*p) += len + 2;
16977     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16978     sscanf(*p, "%[^\"]", *loc);
16979     while (**p && **p != '\"') (*p)++;
16980     if (**p == '\"') (*p)++;
16981     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16982     SendToProgram(buf, cps);
16983     return TRUE;
16984   }
16985   return FALSE;
16986 }
16987
16988 int
16989 ParseOption (Option *opt, ChessProgramState *cps)
16990 // [HGM] options: process the string that defines an engine option, and determine
16991 // name, type, default value, and allowed value range
16992 {
16993         char *p, *q, buf[MSG_SIZ];
16994         int n, min = (-1)<<31, max = 1<<31, def;
16995
16996         opt->target = &opt->value;   // OK for spin/slider and checkbox
16997         if(p = strstr(opt->name, " -spin ")) {
16998             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16999             if(max < min) max = min; // enforce consistency
17000             if(def < min) def = min;
17001             if(def > max) def = max;
17002             opt->value = def;
17003             opt->min = min;
17004             opt->max = max;
17005             opt->type = Spin;
17006         } else if((p = strstr(opt->name, " -slider "))) {
17007             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17008             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17009             if(max < min) max = min; // enforce consistency
17010             if(def < min) def = min;
17011             if(def > max) def = max;
17012             opt->value = def;
17013             opt->min = min;
17014             opt->max = max;
17015             opt->type = Spin; // Slider;
17016         } else if((p = strstr(opt->name, " -string "))) {
17017             opt->textValue = p+9;
17018             opt->type = TextBox;
17019             opt->target = &opt->textValue;
17020         } else if((p = strstr(opt->name, " -file "))) {
17021             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17022             opt->target = opt->textValue = p+7;
17023             opt->type = FileName; // FileName;
17024             opt->target = &opt->textValue;
17025         } else if((p = strstr(opt->name, " -path "))) {
17026             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17027             opt->target = opt->textValue = p+7;
17028             opt->type = PathName; // PathName;
17029             opt->target = &opt->textValue;
17030         } else if(p = strstr(opt->name, " -check ")) {
17031             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17032             opt->value = (def != 0);
17033             opt->type = CheckBox;
17034         } else if(p = strstr(opt->name, " -combo ")) {
17035             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17036             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17037             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17038             opt->value = n = 0;
17039             while(q = StrStr(q, " /// ")) {
17040                 n++; *q = 0;    // count choices, and null-terminate each of them
17041                 q += 5;
17042                 if(*q == '*') { // remember default, which is marked with * prefix
17043                     q++;
17044                     opt->value = n;
17045                 }
17046                 cps->comboList[cps->comboCnt++] = q;
17047             }
17048             cps->comboList[cps->comboCnt++] = NULL;
17049             opt->max = n + 1;
17050             opt->type = ComboBox;
17051         } else if(p = strstr(opt->name, " -button")) {
17052             opt->type = Button;
17053         } else if(p = strstr(opt->name, " -save")) {
17054             opt->type = SaveButton;
17055         } else return FALSE;
17056         *p = 0; // terminate option name
17057         // now look if the command-line options define a setting for this engine option.
17058         if(cps->optionSettings && cps->optionSettings[0])
17059             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17060         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17061           snprintf(buf, MSG_SIZ, "option %s", p);
17062                 if(p = strstr(buf, ",")) *p = 0;
17063                 if(q = strchr(buf, '=')) switch(opt->type) {
17064                     case ComboBox:
17065                         for(n=0; n<opt->max; n++)
17066                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17067                         break;
17068                     case TextBox:
17069                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17070                         break;
17071                     case Spin:
17072                     case CheckBox:
17073                         opt->value = atoi(q+1);
17074                     default:
17075                         break;
17076                 }
17077                 strcat(buf, "\n");
17078                 SendToProgram(buf, cps);
17079         }
17080         return TRUE;
17081 }
17082
17083 void
17084 FeatureDone (ChessProgramState *cps, int val)
17085 {
17086   DelayedEventCallback cb = GetDelayedEvent();
17087   if ((cb == InitBackEnd3 && cps == &first) ||
17088       (cb == SettingsMenuIfReady && cps == &second) ||
17089       (cb == LoadEngine) ||
17090       (cb == TwoMachinesEventIfReady)) {
17091     CancelDelayedEvent();
17092     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17093   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17094   cps->initDone = val;
17095   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17096 }
17097
17098 /* Parse feature command from engine */
17099 void
17100 ParseFeatures (char *args, ChessProgramState *cps)
17101 {
17102   char *p = args;
17103   char *q = NULL;
17104   int val;
17105   char buf[MSG_SIZ];
17106
17107   for (;;) {
17108     while (*p == ' ') p++;
17109     if (*p == NULLCHAR) return;
17110
17111     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17112     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17113     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17114     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17115     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17116     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17117     if (BoolFeature(&p, "reuse", &val, cps)) {
17118       /* Engine can disable reuse, but can't enable it if user said no */
17119       if (!val) cps->reuse = FALSE;
17120       continue;
17121     }
17122     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17123     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17124       if (gameMode == TwoMachinesPlay) {
17125         DisplayTwoMachinesTitle();
17126       } else {
17127         DisplayTitle("");
17128       }
17129       continue;
17130     }
17131     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17132     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17133     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17134     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17135     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17136     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17137     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17138     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17139     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17140     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17141     if (IntFeature(&p, "done", &val, cps)) {
17142       FeatureDone(cps, val);
17143       continue;
17144     }
17145     /* Added by Tord: */
17146     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17147     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17148     /* End of additions by Tord */
17149
17150     /* [HGM] added features: */
17151     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17152     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17153     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17154     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17155     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17156     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17157     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17158     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17159         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17160         FREE(cps->option[cps->nrOptions].name);
17161         cps->option[cps->nrOptions].name = q; q = NULL;
17162         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17163           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17164             SendToProgram(buf, cps);
17165             continue;
17166         }
17167         if(cps->nrOptions >= MAX_OPTIONS) {
17168             cps->nrOptions--;
17169             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17170             DisplayError(buf, 0);
17171         }
17172         continue;
17173     }
17174     /* End of additions by HGM */
17175
17176     /* unknown feature: complain and skip */
17177     q = p;
17178     while (*q && *q != '=') q++;
17179     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17180     SendToProgram(buf, cps);
17181     p = q;
17182     if (*p == '=') {
17183       p++;
17184       if (*p == '\"') {
17185         p++;
17186         while (*p && *p != '\"') p++;
17187         if (*p == '\"') p++;
17188       } else {
17189         while (*p && *p != ' ') p++;
17190       }
17191     }
17192   }
17193
17194 }
17195
17196 void
17197 PeriodicUpdatesEvent (int newState)
17198 {
17199     if (newState == appData.periodicUpdates)
17200       return;
17201
17202     appData.periodicUpdates=newState;
17203
17204     /* Display type changes, so update it now */
17205 //    DisplayAnalysis();
17206
17207     /* Get the ball rolling again... */
17208     if (newState) {
17209         AnalysisPeriodicEvent(1);
17210         StartAnalysisClock();
17211     }
17212 }
17213
17214 void
17215 PonderNextMoveEvent (int newState)
17216 {
17217     if (newState == appData.ponderNextMove) return;
17218     if (gameMode == EditPosition) EditPositionDone(TRUE);
17219     if (newState) {
17220         SendToProgram("hard\n", &first);
17221         if (gameMode == TwoMachinesPlay) {
17222             SendToProgram("hard\n", &second);
17223         }
17224     } else {
17225         SendToProgram("easy\n", &first);
17226         thinkOutput[0] = NULLCHAR;
17227         if (gameMode == TwoMachinesPlay) {
17228             SendToProgram("easy\n", &second);
17229         }
17230     }
17231     appData.ponderNextMove = newState;
17232 }
17233
17234 void
17235 NewSettingEvent (int option, int *feature, char *command, int value)
17236 {
17237     char buf[MSG_SIZ];
17238
17239     if (gameMode == EditPosition) EditPositionDone(TRUE);
17240     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17241     if(feature == NULL || *feature) SendToProgram(buf, &first);
17242     if (gameMode == TwoMachinesPlay) {
17243         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17244     }
17245 }
17246
17247 void
17248 ShowThinkingEvent ()
17249 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17250 {
17251     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17252     int newState = appData.showThinking
17253         // [HGM] thinking: other features now need thinking output as well
17254         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17255
17256     if (oldState == newState) return;
17257     oldState = newState;
17258     if (gameMode == EditPosition) EditPositionDone(TRUE);
17259     if (oldState) {
17260         SendToProgram("post\n", &first);
17261         if (gameMode == TwoMachinesPlay) {
17262             SendToProgram("post\n", &second);
17263         }
17264     } else {
17265         SendToProgram("nopost\n", &first);
17266         thinkOutput[0] = NULLCHAR;
17267         if (gameMode == TwoMachinesPlay) {
17268             SendToProgram("nopost\n", &second);
17269         }
17270     }
17271 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17272 }
17273
17274 void
17275 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17276 {
17277   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17278   if (pr == NoProc) return;
17279   AskQuestion(title, question, replyPrefix, pr);
17280 }
17281
17282 void
17283 TypeInEvent (char firstChar)
17284 {
17285     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17286         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17287         gameMode == AnalyzeMode || gameMode == EditGame ||
17288         gameMode == EditPosition || gameMode == IcsExamining ||
17289         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17290         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17291                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17292                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17293         gameMode == Training) PopUpMoveDialog(firstChar);
17294 }
17295
17296 void
17297 TypeInDoneEvent (char *move)
17298 {
17299         Board board;
17300         int n, fromX, fromY, toX, toY;
17301         char promoChar;
17302         ChessMove moveType;
17303
17304         // [HGM] FENedit
17305         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17306                 EditPositionPasteFEN(move);
17307                 return;
17308         }
17309         // [HGM] movenum: allow move number to be typed in any mode
17310         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17311           ToNrEvent(2*n-1);
17312           return;
17313         }
17314         // undocumented kludge: allow command-line option to be typed in!
17315         // (potentially fatal, and does not implement the effect of the option.)
17316         // should only be used for options that are values on which future decisions will be made,
17317         // and definitely not on options that would be used during initialization.
17318         if(strstr(move, "!!! -") == move) {
17319             ParseArgsFromString(move+4);
17320             return;
17321         }
17322
17323       if (gameMode != EditGame && currentMove != forwardMostMove &&
17324         gameMode != Training) {
17325         DisplayMoveError(_("Displayed move is not current"));
17326       } else {
17327         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17328           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17329         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17330         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17331           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17332           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17333         } else {
17334           DisplayMoveError(_("Could not parse move"));
17335         }
17336       }
17337 }
17338
17339 void
17340 DisplayMove (int moveNumber)
17341 {
17342     char message[MSG_SIZ];
17343     char res[MSG_SIZ];
17344     char cpThinkOutput[MSG_SIZ];
17345
17346     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17347
17348     if (moveNumber == forwardMostMove - 1 ||
17349         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17350
17351         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17352
17353         if (strchr(cpThinkOutput, '\n')) {
17354             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17355         }
17356     } else {
17357         *cpThinkOutput = NULLCHAR;
17358     }
17359
17360     /* [AS] Hide thinking from human user */
17361     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17362         *cpThinkOutput = NULLCHAR;
17363         if( thinkOutput[0] != NULLCHAR ) {
17364             int i;
17365
17366             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17367                 cpThinkOutput[i] = '.';
17368             }
17369             cpThinkOutput[i] = NULLCHAR;
17370             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17371         }
17372     }
17373
17374     if (moveNumber == forwardMostMove - 1 &&
17375         gameInfo.resultDetails != NULL) {
17376         if (gameInfo.resultDetails[0] == NULLCHAR) {
17377           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17378         } else {
17379           snprintf(res, MSG_SIZ, " {%s} %s",
17380                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17381         }
17382     } else {
17383         res[0] = NULLCHAR;
17384     }
17385
17386     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17387         DisplayMessage(res, cpThinkOutput);
17388     } else {
17389       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17390                 WhiteOnMove(moveNumber) ? " " : ".. ",
17391                 parseList[moveNumber], res);
17392         DisplayMessage(message, cpThinkOutput);
17393     }
17394 }
17395
17396 void
17397 DisplayComment (int moveNumber, char *text)
17398 {
17399     char title[MSG_SIZ];
17400
17401     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17402       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17403     } else {
17404       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17405               WhiteOnMove(moveNumber) ? " " : ".. ",
17406               parseList[moveNumber]);
17407     }
17408     if (text != NULL && (appData.autoDisplayComment || commentUp))
17409         CommentPopUp(title, text);
17410 }
17411
17412 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17413  * might be busy thinking or pondering.  It can be omitted if your
17414  * gnuchess is configured to stop thinking immediately on any user
17415  * input.  However, that gnuchess feature depends on the FIONREAD
17416  * ioctl, which does not work properly on some flavors of Unix.
17417  */
17418 void
17419 Attention (ChessProgramState *cps)
17420 {
17421 #if ATTENTION
17422     if (!cps->useSigint) return;
17423     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17424     switch (gameMode) {
17425       case MachinePlaysWhite:
17426       case MachinePlaysBlack:
17427       case TwoMachinesPlay:
17428       case IcsPlayingWhite:
17429       case IcsPlayingBlack:
17430       case AnalyzeMode:
17431       case AnalyzeFile:
17432         /* Skip if we know it isn't thinking */
17433         if (!cps->maybeThinking) return;
17434         if (appData.debugMode)
17435           fprintf(debugFP, "Interrupting %s\n", cps->which);
17436         InterruptChildProcess(cps->pr);
17437         cps->maybeThinking = FALSE;
17438         break;
17439       default:
17440         break;
17441     }
17442 #endif /*ATTENTION*/
17443 }
17444
17445 int
17446 CheckFlags ()
17447 {
17448     if (whiteTimeRemaining <= 0) {
17449         if (!whiteFlag) {
17450             whiteFlag = TRUE;
17451             if (appData.icsActive) {
17452                 if (appData.autoCallFlag &&
17453                     gameMode == IcsPlayingBlack && !blackFlag) {
17454                   SendToICS(ics_prefix);
17455                   SendToICS("flag\n");
17456                 }
17457             } else {
17458                 if (blackFlag) {
17459                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17460                 } else {
17461                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17462                     if (appData.autoCallFlag) {
17463                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17464                         return TRUE;
17465                     }
17466                 }
17467             }
17468         }
17469     }
17470     if (blackTimeRemaining <= 0) {
17471         if (!blackFlag) {
17472             blackFlag = TRUE;
17473             if (appData.icsActive) {
17474                 if (appData.autoCallFlag &&
17475                     gameMode == IcsPlayingWhite && !whiteFlag) {
17476                   SendToICS(ics_prefix);
17477                   SendToICS("flag\n");
17478                 }
17479             } else {
17480                 if (whiteFlag) {
17481                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17482                 } else {
17483                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17484                     if (appData.autoCallFlag) {
17485                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17486                         return TRUE;
17487                     }
17488                 }
17489             }
17490         }
17491     }
17492     return FALSE;
17493 }
17494
17495 void
17496 CheckTimeControl ()
17497 {
17498     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17499         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17500
17501     /*
17502      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17503      */
17504     if ( !WhiteOnMove(forwardMostMove) ) {
17505         /* White made time control */
17506         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17507         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17508         /* [HGM] time odds: correct new time quota for time odds! */
17509                                             / WhitePlayer()->timeOdds;
17510         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17511     } else {
17512         lastBlack -= blackTimeRemaining;
17513         /* Black made time control */
17514         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17515                                             / WhitePlayer()->other->timeOdds;
17516         lastWhite = whiteTimeRemaining;
17517     }
17518 }
17519
17520 void
17521 DisplayBothClocks ()
17522 {
17523     int wom = gameMode == EditPosition ?
17524       !blackPlaysFirst : WhiteOnMove(currentMove);
17525     DisplayWhiteClock(whiteTimeRemaining, wom);
17526     DisplayBlackClock(blackTimeRemaining, !wom);
17527 }
17528
17529
17530 /* Timekeeping seems to be a portability nightmare.  I think everyone
17531    has ftime(), but I'm really not sure, so I'm including some ifdefs
17532    to use other calls if you don't.  Clocks will be less accurate if
17533    you have neither ftime nor gettimeofday.
17534 */
17535
17536 /* VS 2008 requires the #include outside of the function */
17537 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17538 #include <sys/timeb.h>
17539 #endif
17540
17541 /* Get the current time as a TimeMark */
17542 void
17543 GetTimeMark (TimeMark *tm)
17544 {
17545 #if HAVE_GETTIMEOFDAY
17546
17547     struct timeval timeVal;
17548     struct timezone timeZone;
17549
17550     gettimeofday(&timeVal, &timeZone);
17551     tm->sec = (long) timeVal.tv_sec;
17552     tm->ms = (int) (timeVal.tv_usec / 1000L);
17553
17554 #else /*!HAVE_GETTIMEOFDAY*/
17555 #if HAVE_FTIME
17556
17557 // include <sys/timeb.h> / moved to just above start of function
17558     struct timeb timeB;
17559
17560     ftime(&timeB);
17561     tm->sec = (long) timeB.time;
17562     tm->ms = (int) timeB.millitm;
17563
17564 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17565     tm->sec = (long) time(NULL);
17566     tm->ms = 0;
17567 #endif
17568 #endif
17569 }
17570
17571 /* Return the difference in milliseconds between two
17572    time marks.  We assume the difference will fit in a long!
17573 */
17574 long
17575 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17576 {
17577     return 1000L*(tm2->sec - tm1->sec) +
17578            (long) (tm2->ms - tm1->ms);
17579 }
17580
17581
17582 /*
17583  * Code to manage the game clocks.
17584  *
17585  * In tournament play, black starts the clock and then white makes a move.
17586  * We give the human user a slight advantage if he is playing white---the
17587  * clocks don't run until he makes his first move, so it takes zero time.
17588  * Also, we don't account for network lag, so we could get out of sync
17589  * with GNU Chess's clock -- but then, referees are always right.
17590  */
17591
17592 static TimeMark tickStartTM;
17593 static long intendedTickLength;
17594
17595 long
17596 NextTickLength (long timeRemaining)
17597 {
17598     long nominalTickLength, nextTickLength;
17599
17600     if (timeRemaining > 0L && timeRemaining <= 10000L)
17601       nominalTickLength = 100L;
17602     else
17603       nominalTickLength = 1000L;
17604     nextTickLength = timeRemaining % nominalTickLength;
17605     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17606
17607     return nextTickLength;
17608 }
17609
17610 /* Adjust clock one minute up or down */
17611 void
17612 AdjustClock (Boolean which, int dir)
17613 {
17614     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17615     if(which) blackTimeRemaining += 60000*dir;
17616     else      whiteTimeRemaining += 60000*dir;
17617     DisplayBothClocks();
17618     adjustedClock = TRUE;
17619 }
17620
17621 /* Stop clocks and reset to a fresh time control */
17622 void
17623 ResetClocks ()
17624 {
17625     (void) StopClockTimer();
17626     if (appData.icsActive) {
17627         whiteTimeRemaining = blackTimeRemaining = 0;
17628     } else if (searchTime) {
17629         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17630         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17631     } else { /* [HGM] correct new time quote for time odds */
17632         whiteTC = blackTC = fullTimeControlString;
17633         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17634         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17635     }
17636     if (whiteFlag || blackFlag) {
17637         DisplayTitle("");
17638         whiteFlag = blackFlag = FALSE;
17639     }
17640     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17641     DisplayBothClocks();
17642     adjustedClock = FALSE;
17643 }
17644
17645 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17646
17647 /* Decrement running clock by amount of time that has passed */
17648 void
17649 DecrementClocks ()
17650 {
17651     long timeRemaining;
17652     long lastTickLength, fudge;
17653     TimeMark now;
17654
17655     if (!appData.clockMode) return;
17656     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17657
17658     GetTimeMark(&now);
17659
17660     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17661
17662     /* Fudge if we woke up a little too soon */
17663     fudge = intendedTickLength - lastTickLength;
17664     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17665
17666     if (WhiteOnMove(forwardMostMove)) {
17667         if(whiteNPS >= 0) lastTickLength = 0;
17668         timeRemaining = whiteTimeRemaining -= lastTickLength;
17669         if(timeRemaining < 0 && !appData.icsActive) {
17670             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17671             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17672                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17673                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17674             }
17675         }
17676         DisplayWhiteClock(whiteTimeRemaining - fudge,
17677                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17678     } else {
17679         if(blackNPS >= 0) lastTickLength = 0;
17680         timeRemaining = blackTimeRemaining -= lastTickLength;
17681         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17682             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17683             if(suddenDeath) {
17684                 blackStartMove = forwardMostMove;
17685                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17686             }
17687         }
17688         DisplayBlackClock(blackTimeRemaining - fudge,
17689                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17690     }
17691     if (CheckFlags()) return;
17692
17693     if(twoBoards) { // count down secondary board's clocks as well
17694         activePartnerTime -= lastTickLength;
17695         partnerUp = 1;
17696         if(activePartner == 'W')
17697             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17698         else
17699             DisplayBlackClock(activePartnerTime, TRUE);
17700         partnerUp = 0;
17701     }
17702
17703     tickStartTM = now;
17704     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17705     StartClockTimer(intendedTickLength);
17706
17707     /* if the time remaining has fallen below the alarm threshold, sound the
17708      * alarm. if the alarm has sounded and (due to a takeback or time control
17709      * with increment) the time remaining has increased to a level above the
17710      * threshold, reset the alarm so it can sound again.
17711      */
17712
17713     if (appData.icsActive && appData.icsAlarm) {
17714
17715         /* make sure we are dealing with the user's clock */
17716         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17717                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17718            )) return;
17719
17720         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17721             alarmSounded = FALSE;
17722         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17723             PlayAlarmSound();
17724             alarmSounded = TRUE;
17725         }
17726     }
17727 }
17728
17729
17730 /* A player has just moved, so stop the previously running
17731    clock and (if in clock mode) start the other one.
17732    We redisplay both clocks in case we're in ICS mode, because
17733    ICS gives us an update to both clocks after every move.
17734    Note that this routine is called *after* forwardMostMove
17735    is updated, so the last fractional tick must be subtracted
17736    from the color that is *not* on move now.
17737 */
17738 void
17739 SwitchClocks (int newMoveNr)
17740 {
17741     long lastTickLength;
17742     TimeMark now;
17743     int flagged = FALSE;
17744
17745     GetTimeMark(&now);
17746
17747     if (StopClockTimer() && appData.clockMode) {
17748         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17749         if (!WhiteOnMove(forwardMostMove)) {
17750             if(blackNPS >= 0) lastTickLength = 0;
17751             blackTimeRemaining -= lastTickLength;
17752            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17753 //         if(pvInfoList[forwardMostMove].time == -1)
17754                  pvInfoList[forwardMostMove].time =               // use GUI time
17755                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17756         } else {
17757            if(whiteNPS >= 0) lastTickLength = 0;
17758            whiteTimeRemaining -= lastTickLength;
17759            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17760 //         if(pvInfoList[forwardMostMove].time == -1)
17761                  pvInfoList[forwardMostMove].time =
17762                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17763         }
17764         flagged = CheckFlags();
17765     }
17766     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17767     CheckTimeControl();
17768
17769     if (flagged || !appData.clockMode) return;
17770
17771     switch (gameMode) {
17772       case MachinePlaysBlack:
17773       case MachinePlaysWhite:
17774       case BeginningOfGame:
17775         if (pausing) return;
17776         break;
17777
17778       case EditGame:
17779       case PlayFromGameFile:
17780       case IcsExamining:
17781         return;
17782
17783       default:
17784         break;
17785     }
17786
17787     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17788         if(WhiteOnMove(forwardMostMove))
17789              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17790         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17791     }
17792
17793     tickStartTM = now;
17794     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17795       whiteTimeRemaining : blackTimeRemaining);
17796     StartClockTimer(intendedTickLength);
17797 }
17798
17799
17800 /* Stop both clocks */
17801 void
17802 StopClocks ()
17803 {
17804     long lastTickLength;
17805     TimeMark now;
17806
17807     if (!StopClockTimer()) return;
17808     if (!appData.clockMode) return;
17809
17810     GetTimeMark(&now);
17811
17812     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17813     if (WhiteOnMove(forwardMostMove)) {
17814         if(whiteNPS >= 0) lastTickLength = 0;
17815         whiteTimeRemaining -= lastTickLength;
17816         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17817     } else {
17818         if(blackNPS >= 0) lastTickLength = 0;
17819         blackTimeRemaining -= lastTickLength;
17820         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17821     }
17822     CheckFlags();
17823 }
17824
17825 /* Start clock of player on move.  Time may have been reset, so
17826    if clock is already running, stop and restart it. */
17827 void
17828 StartClocks ()
17829 {
17830     (void) StopClockTimer(); /* in case it was running already */
17831     DisplayBothClocks();
17832     if (CheckFlags()) return;
17833
17834     if (!appData.clockMode) return;
17835     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17836
17837     GetTimeMark(&tickStartTM);
17838     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17839       whiteTimeRemaining : blackTimeRemaining);
17840
17841    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17842     whiteNPS = blackNPS = -1;
17843     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17844        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17845         whiteNPS = first.nps;
17846     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17847        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17848         blackNPS = first.nps;
17849     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17850         whiteNPS = second.nps;
17851     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17852         blackNPS = second.nps;
17853     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17854
17855     StartClockTimer(intendedTickLength);
17856 }
17857
17858 char *
17859 TimeString (long ms)
17860 {
17861     long second, minute, hour, day;
17862     char *sign = "";
17863     static char buf[32];
17864
17865     if (ms > 0 && ms <= 9900) {
17866       /* convert milliseconds to tenths, rounding up */
17867       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17868
17869       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17870       return buf;
17871     }
17872
17873     /* convert milliseconds to seconds, rounding up */
17874     /* use floating point to avoid strangeness of integer division
17875        with negative dividends on many machines */
17876     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17877
17878     if (second < 0) {
17879         sign = "-";
17880         second = -second;
17881     }
17882
17883     day = second / (60 * 60 * 24);
17884     second = second % (60 * 60 * 24);
17885     hour = second / (60 * 60);
17886     second = second % (60 * 60);
17887     minute = second / 60;
17888     second = second % 60;
17889
17890     if (day > 0)
17891       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17892               sign, day, hour, minute, second);
17893     else if (hour > 0)
17894       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17895     else
17896       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17897
17898     return buf;
17899 }
17900
17901
17902 /*
17903  * This is necessary because some C libraries aren't ANSI C compliant yet.
17904  */
17905 char *
17906 StrStr (char *string, char *match)
17907 {
17908     int i, length;
17909
17910     length = strlen(match);
17911
17912     for (i = strlen(string) - length; i >= 0; i--, string++)
17913       if (!strncmp(match, string, length))
17914         return string;
17915
17916     return NULL;
17917 }
17918
17919 char *
17920 StrCaseStr (char *string, char *match)
17921 {
17922     int i, j, length;
17923
17924     length = strlen(match);
17925
17926     for (i = strlen(string) - length; i >= 0; i--, string++) {
17927         for (j = 0; j < length; j++) {
17928             if (ToLower(match[j]) != ToLower(string[j]))
17929               break;
17930         }
17931         if (j == length) return string;
17932     }
17933
17934     return NULL;
17935 }
17936
17937 #ifndef _amigados
17938 int
17939 StrCaseCmp (char *s1, char *s2)
17940 {
17941     char c1, c2;
17942
17943     for (;;) {
17944         c1 = ToLower(*s1++);
17945         c2 = ToLower(*s2++);
17946         if (c1 > c2) return 1;
17947         if (c1 < c2) return -1;
17948         if (c1 == NULLCHAR) return 0;
17949     }
17950 }
17951
17952
17953 int
17954 ToLower (int c)
17955 {
17956     return isupper(c) ? tolower(c) : c;
17957 }
17958
17959
17960 int
17961 ToUpper (int c)
17962 {
17963     return islower(c) ? toupper(c) : c;
17964 }
17965 #endif /* !_amigados    */
17966
17967 char *
17968 StrSave (char *s)
17969 {
17970   char *ret;
17971
17972   if ((ret = (char *) malloc(strlen(s) + 1)))
17973     {
17974       safeStrCpy(ret, s, strlen(s)+1);
17975     }
17976   return ret;
17977 }
17978
17979 char *
17980 StrSavePtr (char *s, char **savePtr)
17981 {
17982     if (*savePtr) {
17983         free(*savePtr);
17984     }
17985     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17986       safeStrCpy(*savePtr, s, strlen(s)+1);
17987     }
17988     return(*savePtr);
17989 }
17990
17991 char *
17992 PGNDate ()
17993 {
17994     time_t clock;
17995     struct tm *tm;
17996     char buf[MSG_SIZ];
17997
17998     clock = time((time_t *)NULL);
17999     tm = localtime(&clock);
18000     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18001             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18002     return StrSave(buf);
18003 }
18004
18005
18006 char *
18007 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18008 {
18009     int i, j, fromX, fromY, toX, toY;
18010     int whiteToPlay, haveRights = nrCastlingRights;
18011     char buf[MSG_SIZ];
18012     char *p, *q;
18013     int emptycount;
18014     ChessSquare piece;
18015
18016     whiteToPlay = (gameMode == EditPosition) ?
18017       !blackPlaysFirst : (move % 2 == 0);
18018     p = buf;
18019
18020     /* Piece placement data */
18021     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18022         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18023         emptycount = 0;
18024         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18025             if (boards[move][i][j] == EmptySquare) {
18026                 emptycount++;
18027             } else { ChessSquare piece = boards[move][i][j];
18028                 if (emptycount > 0) {
18029                     if(emptycount<10) /* [HGM] can be >= 10 */
18030                         *p++ = '0' + emptycount;
18031                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18032                     emptycount = 0;
18033                 }
18034                 if(PieceToChar(piece) == '+') {
18035                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18036                     *p++ = '+';
18037                     piece = (ChessSquare)(CHUDEMOTED(piece));
18038                 }
18039                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18040                 if(*p = PieceSuffix(piece)) p++;
18041                 if(p[-1] == '~') {
18042                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18043                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18044                     *p++ = '~';
18045                 }
18046             }
18047         }
18048         if (emptycount > 0) {
18049             if(emptycount<10) /* [HGM] can be >= 10 */
18050                 *p++ = '0' + emptycount;
18051             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18052             emptycount = 0;
18053         }
18054         *p++ = '/';
18055     }
18056     *(p - 1) = ' ';
18057
18058     /* [HGM] print Crazyhouse or Shogi holdings */
18059     if( gameInfo.holdingsWidth ) {
18060         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18061         q = p;
18062         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18063             piece = boards[move][i][BOARD_WIDTH-1];
18064             if( piece != EmptySquare )
18065               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18066                   *p++ = PieceToChar(piece);
18067         }
18068         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18069             piece = boards[move][BOARD_HEIGHT-i-1][0];
18070             if( piece != EmptySquare )
18071               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18072                   *p++ = PieceToChar(piece);
18073         }
18074
18075         if( q == p ) *p++ = '-';
18076         *p++ = ']';
18077         *p++ = ' ';
18078     }
18079
18080     /* Active color */
18081     *p++ = whiteToPlay ? 'w' : 'b';
18082     *p++ = ' ';
18083
18084   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18085     haveRights = 0; q = p;
18086     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18087       piece = boards[move][0][i];
18088       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18089         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18090       }
18091     }
18092     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18093       piece = boards[move][BOARD_HEIGHT-1][i];
18094       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18095         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18096       }
18097     }
18098     if(p == q) *p++ = '-';
18099     *p++ = ' ';
18100   }
18101
18102   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18103     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18104   } else {
18105   if(haveRights) {
18106      int handW=0, handB=0;
18107      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18108         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18109         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18110      }
18111      q = p;
18112      if(appData.fischerCastling) {
18113         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18114            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18115                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18116         } else {
18117        /* [HGM] write directly from rights */
18118            if(boards[move][CASTLING][2] != NoRights &&
18119               boards[move][CASTLING][0] != NoRights   )
18120                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18121            if(boards[move][CASTLING][2] != NoRights &&
18122               boards[move][CASTLING][1] != NoRights   )
18123                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18124         }
18125         if(handB) {
18126            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18127                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18128         } else {
18129            if(boards[move][CASTLING][5] != NoRights &&
18130               boards[move][CASTLING][3] != NoRights   )
18131                 *p++ = boards[move][CASTLING][3] + AAA;
18132            if(boards[move][CASTLING][5] != NoRights &&
18133               boards[move][CASTLING][4] != NoRights   )
18134                 *p++ = boards[move][CASTLING][4] + AAA;
18135         }
18136      } else {
18137
18138         /* [HGM] write true castling rights */
18139         if( nrCastlingRights == 6 ) {
18140             int q, k=0;
18141             if(boards[move][CASTLING][0] != NoRights &&
18142                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18143             q = (boards[move][CASTLING][1] != NoRights &&
18144                  boards[move][CASTLING][2] != NoRights  );
18145             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18146                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18147                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18148                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18149             }
18150             if(q) *p++ = 'Q';
18151             k = 0;
18152             if(boards[move][CASTLING][3] != NoRights &&
18153                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18154             q = (boards[move][CASTLING][4] != NoRights &&
18155                  boards[move][CASTLING][5] != NoRights  );
18156             if(handB) {
18157                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18158                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18159                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18160             }
18161             if(q) *p++ = 'q';
18162         }
18163      }
18164      if (q == p) *p++ = '-'; /* No castling rights */
18165      *p++ = ' ';
18166   }
18167
18168   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18169      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18170      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18171     /* En passant target square */
18172     if (move > backwardMostMove) {
18173         fromX = moveList[move - 1][0] - AAA;
18174         fromY = moveList[move - 1][1] - ONE;
18175         toX = moveList[move - 1][2] - AAA;
18176         toY = moveList[move - 1][3] - ONE;
18177         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18178             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18179             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18180             fromX == toX) {
18181             /* 2-square pawn move just happened */
18182             *p++ = toX + AAA;
18183             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18184         } else {
18185             *p++ = '-';
18186         }
18187     } else if(move == backwardMostMove) {
18188         // [HGM] perhaps we should always do it like this, and forget the above?
18189         if((signed char)boards[move][EP_STATUS] >= 0) {
18190             *p++ = boards[move][EP_STATUS] + AAA;
18191             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18192         } else {
18193             *p++ = '-';
18194         }
18195     } else {
18196         *p++ = '-';
18197     }
18198     *p++ = ' ';
18199   }
18200   }
18201
18202     if(moveCounts)
18203     {   int i = 0, j=move;
18204
18205         /* [HGM] find reversible plies */
18206         if (appData.debugMode) { int k;
18207             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18208             for(k=backwardMostMove; k<=forwardMostMove; k++)
18209                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18210
18211         }
18212
18213         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18214         if( j == backwardMostMove ) i += initialRulePlies;
18215         sprintf(p, "%d ", i);
18216         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18217
18218         /* Fullmove number */
18219         sprintf(p, "%d", (move / 2) + 1);
18220     } else *--p = NULLCHAR;
18221
18222     return StrSave(buf);
18223 }
18224
18225 Boolean
18226 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18227 {
18228     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18229     char *p, c;
18230     int emptycount, virgin[BOARD_FILES];
18231     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18232
18233     p = fen;
18234
18235     /* Piece placement data */
18236     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18237         j = 0;
18238         for (;;) {
18239             if (*p == '/' || *p == ' ' || *p == '[' ) {
18240                 if(j > w) w = j;
18241                 emptycount = gameInfo.boardWidth - j;
18242                 while (emptycount--)
18243                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18244                 if (*p == '/') p++;
18245                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18246                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18247                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18248                     }
18249                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18250                 }
18251                 break;
18252 #if(BOARD_FILES >= 10)*0
18253             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18254                 p++; emptycount=10;
18255                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18256                 while (emptycount--)
18257                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18258 #endif
18259             } else if (*p == '*') {
18260                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18261             } else if (isdigit(*p)) {
18262                 emptycount = *p++ - '0';
18263                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18264                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18265                 while (emptycount--)
18266                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18267             } else if (*p == '<') {
18268                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18269                 else if (i != 0 || !shuffle) return FALSE;
18270                 p++;
18271             } else if (shuffle && *p == '>') {
18272                 p++; // for now ignore closing shuffle range, and assume rank-end
18273             } else if (*p == '?') {
18274                 if (j >= gameInfo.boardWidth) return FALSE;
18275                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18276                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18277             } else if (*p == '+' || isalpha(*p)) {
18278                 char *q, *s = SUFFIXES;
18279                 if (j >= gameInfo.boardWidth) return FALSE;
18280                 if(*p=='+') {
18281                     char c = *++p;
18282                     if(q = strchr(s, p[1])) p++;
18283                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18284                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18285                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18286                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18287                 } else {
18288                     char c = *p++;
18289                     if(q = strchr(s, *p)) p++;
18290                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18291                 }
18292
18293                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18294                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18295                     piece = (ChessSquare) (PROMOTED(piece));
18296                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18297                     p++;
18298                 }
18299                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18300                 if(piece == king) wKingRank = i;
18301                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18302             } else {
18303                 return FALSE;
18304             }
18305         }
18306     }
18307     while (*p == '/' || *p == ' ') p++;
18308
18309     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18310
18311     /* [HGM] by default clear Crazyhouse holdings, if present */
18312     if(gameInfo.holdingsWidth) {
18313        for(i=0; i<BOARD_HEIGHT; i++) {
18314            board[i][0]             = EmptySquare; /* black holdings */
18315            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18316            board[i][1]             = (ChessSquare) 0; /* black counts */
18317            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18318        }
18319     }
18320
18321     /* [HGM] look for Crazyhouse holdings here */
18322     while(*p==' ') p++;
18323     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18324         int swap=0, wcnt=0, bcnt=0;
18325         if(*p == '[') p++;
18326         if(*p == '<') swap++, p++;
18327         if(*p == '-' ) p++; /* empty holdings */ else {
18328             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18329             /* if we would allow FEN reading to set board size, we would   */
18330             /* have to add holdings and shift the board read so far here   */
18331             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18332                 p++;
18333                 if((int) piece >= (int) BlackPawn ) {
18334                     i = (int)piece - (int)BlackPawn;
18335                     i = PieceToNumber((ChessSquare)i);
18336                     if( i >= gameInfo.holdingsSize ) return FALSE;
18337                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18338                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18339                     bcnt++;
18340                 } else {
18341                     i = (int)piece - (int)WhitePawn;
18342                     i = PieceToNumber((ChessSquare)i);
18343                     if( i >= gameInfo.holdingsSize ) return FALSE;
18344                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18345                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18346                     wcnt++;
18347                 }
18348             }
18349             if(subst) { // substitute back-rank question marks by holdings pieces
18350                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18351                     int k, m, n = bcnt + 1;
18352                     if(board[0][j] == ClearBoard) {
18353                         if(!wcnt) return FALSE;
18354                         n = rand() % wcnt;
18355                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18356                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18357                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18358                             break;
18359                         }
18360                     }
18361                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18362                         if(!bcnt) return FALSE;
18363                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18364                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18365                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18366                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18367                             break;
18368                         }
18369                     }
18370                 }
18371                 subst = 0;
18372             }
18373         }
18374         if(*p == ']') p++;
18375     }
18376
18377     if(subst) return FALSE; // substitution requested, but no holdings
18378
18379     while(*p == ' ') p++;
18380
18381     /* Active color */
18382     c = *p++;
18383     if(appData.colorNickNames) {
18384       if( c == appData.colorNickNames[0] ) c = 'w'; else
18385       if( c == appData.colorNickNames[1] ) c = 'b';
18386     }
18387     switch (c) {
18388       case 'w':
18389         *blackPlaysFirst = FALSE;
18390         break;
18391       case 'b':
18392         *blackPlaysFirst = TRUE;
18393         break;
18394       default:
18395         return FALSE;
18396     }
18397
18398     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18399     /* return the extra info in global variiables             */
18400
18401     while(*p==' ') p++;
18402
18403     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18404         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18405         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18406     }
18407
18408     /* set defaults in case FEN is incomplete */
18409     board[EP_STATUS] = EP_UNKNOWN;
18410     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18411     for(i=0; i<nrCastlingRights; i++ ) {
18412         board[CASTLING][i] =
18413             appData.fischerCastling ? NoRights : initialRights[i];
18414     }   /* assume possible unless obviously impossible */
18415     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18416     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18417     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18418                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18419     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18420     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18421     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18422                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18423     FENrulePlies = 0;
18424
18425     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18426       char *q = p;
18427       int w=0, b=0;
18428       while(isalpha(*p)) {
18429         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18430         if(islower(*p)) b |= 1 << (*p++ - 'a');
18431       }
18432       if(*p == '-') p++;
18433       if(p != q) {
18434         board[TOUCHED_W] = ~w;
18435         board[TOUCHED_B] = ~b;
18436         while(*p == ' ') p++;
18437       }
18438     } else
18439
18440     if(nrCastlingRights) {
18441       int fischer = 0;
18442       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18443       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18444           /* castling indicator present, so default becomes no castlings */
18445           for(i=0; i<nrCastlingRights; i++ ) {
18446                  board[CASTLING][i] = NoRights;
18447           }
18448       }
18449       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18450              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18451              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18452              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18453         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18454
18455         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18456             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18457             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18458         }
18459         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18460             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18461         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18462                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18463         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18464                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18465         switch(c) {
18466           case'K':
18467               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18468               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18469               board[CASTLING][2] = whiteKingFile;
18470               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18471               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18472               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18473               break;
18474           case'Q':
18475               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18476               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18477               board[CASTLING][2] = whiteKingFile;
18478               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18479               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18480               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18481               break;
18482           case'k':
18483               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18484               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18485               board[CASTLING][5] = blackKingFile;
18486               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18487               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18488               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18489               break;
18490           case'q':
18491               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18492               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18493               board[CASTLING][5] = blackKingFile;
18494               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18495               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18496               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18497           case '-':
18498               break;
18499           default: /* FRC castlings */
18500               if(c >= 'a') { /* black rights */
18501                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18502                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18503                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18504                   if(i == BOARD_RGHT) break;
18505                   board[CASTLING][5] = i;
18506                   c -= AAA;
18507                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18508                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18509                   if(c > i)
18510                       board[CASTLING][3] = c;
18511                   else
18512                       board[CASTLING][4] = c;
18513               } else { /* white rights */
18514                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18515                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18516                     if(board[0][i] == WhiteKing) break;
18517                   if(i == BOARD_RGHT) break;
18518                   board[CASTLING][2] = i;
18519                   c -= AAA - 'a' + 'A';
18520                   if(board[0][c] >= WhiteKing) break;
18521                   if(c > i)
18522                       board[CASTLING][0] = c;
18523                   else
18524                       board[CASTLING][1] = c;
18525               }
18526         }
18527       }
18528       for(i=0; i<nrCastlingRights; i++)
18529         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18530       if(gameInfo.variant == VariantSChess)
18531         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18532       if(fischer && shuffle) appData.fischerCastling = TRUE;
18533     if (appData.debugMode) {
18534         fprintf(debugFP, "FEN castling rights:");
18535         for(i=0; i<nrCastlingRights; i++)
18536         fprintf(debugFP, " %d", board[CASTLING][i]);
18537         fprintf(debugFP, "\n");
18538     }
18539
18540       while(*p==' ') p++;
18541     }
18542
18543     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18544
18545     /* read e.p. field in games that know e.p. capture */
18546     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18547        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18548        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18549       if(*p=='-') {
18550         p++; board[EP_STATUS] = EP_NONE;
18551       } else {
18552          char c = *p++ - AAA;
18553
18554          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18555          if(*p >= '0' && *p <='9') p++;
18556          board[EP_STATUS] = c;
18557       }
18558     }
18559
18560
18561     if(sscanf(p, "%d", &i) == 1) {
18562         FENrulePlies = i; /* 50-move ply counter */
18563         /* (The move number is still ignored)    */
18564     }
18565
18566     return TRUE;
18567 }
18568
18569 void
18570 EditPositionPasteFEN (char *fen)
18571 {
18572   if (fen != NULL) {
18573     Board initial_position;
18574
18575     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18576       DisplayError(_("Bad FEN position in clipboard"), 0);
18577       return ;
18578     } else {
18579       int savedBlackPlaysFirst = blackPlaysFirst;
18580       EditPositionEvent();
18581       blackPlaysFirst = savedBlackPlaysFirst;
18582       CopyBoard(boards[0], initial_position);
18583       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18584       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18585       DisplayBothClocks();
18586       DrawPosition(FALSE, boards[currentMove]);
18587     }
18588   }
18589 }
18590
18591 static char cseq[12] = "\\   ";
18592
18593 Boolean
18594 set_cont_sequence (char *new_seq)
18595 {
18596     int len;
18597     Boolean ret;
18598
18599     // handle bad attempts to set the sequence
18600         if (!new_seq)
18601                 return 0; // acceptable error - no debug
18602
18603     len = strlen(new_seq);
18604     ret = (len > 0) && (len < sizeof(cseq));
18605     if (ret)
18606       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18607     else if (appData.debugMode)
18608       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18609     return ret;
18610 }
18611
18612 /*
18613     reformat a source message so words don't cross the width boundary.  internal
18614     newlines are not removed.  returns the wrapped size (no null character unless
18615     included in source message).  If dest is NULL, only calculate the size required
18616     for the dest buffer.  lp argument indicats line position upon entry, and it's
18617     passed back upon exit.
18618 */
18619 int
18620 wrap (char *dest, char *src, int count, int width, int *lp)
18621 {
18622     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18623
18624     cseq_len = strlen(cseq);
18625     old_line = line = *lp;
18626     ansi = len = clen = 0;
18627
18628     for (i=0; i < count; i++)
18629     {
18630         if (src[i] == '\033')
18631             ansi = 1;
18632
18633         // if we hit the width, back up
18634         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18635         {
18636             // store i & len in case the word is too long
18637             old_i = i, old_len = len;
18638
18639             // find the end of the last word
18640             while (i && src[i] != ' ' && src[i] != '\n')
18641             {
18642                 i--;
18643                 len--;
18644             }
18645
18646             // word too long?  restore i & len before splitting it
18647             if ((old_i-i+clen) >= width)
18648             {
18649                 i = old_i;
18650                 len = old_len;
18651             }
18652
18653             // extra space?
18654             if (i && src[i-1] == ' ')
18655                 len--;
18656
18657             if (src[i] != ' ' && src[i] != '\n')
18658             {
18659                 i--;
18660                 if (len)
18661                     len--;
18662             }
18663
18664             // now append the newline and continuation sequence
18665             if (dest)
18666                 dest[len] = '\n';
18667             len++;
18668             if (dest)
18669                 strncpy(dest+len, cseq, cseq_len);
18670             len += cseq_len;
18671             line = cseq_len;
18672             clen = cseq_len;
18673             continue;
18674         }
18675
18676         if (dest)
18677             dest[len] = src[i];
18678         len++;
18679         if (!ansi)
18680             line++;
18681         if (src[i] == '\n')
18682             line = 0;
18683         if (src[i] == 'm')
18684             ansi = 0;
18685     }
18686     if (dest && appData.debugMode)
18687     {
18688         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18689             count, width, line, len, *lp);
18690         show_bytes(debugFP, src, count);
18691         fprintf(debugFP, "\ndest: ");
18692         show_bytes(debugFP, dest, len);
18693         fprintf(debugFP, "\n");
18694     }
18695     *lp = dest ? line : old_line;
18696
18697     return len;
18698 }
18699
18700 // [HGM] vari: routines for shelving variations
18701 Boolean modeRestore = FALSE;
18702
18703 void
18704 PushInner (int firstMove, int lastMove)
18705 {
18706         int i, j, nrMoves = lastMove - firstMove;
18707
18708         // push current tail of game on stack
18709         savedResult[storedGames] = gameInfo.result;
18710         savedDetails[storedGames] = gameInfo.resultDetails;
18711         gameInfo.resultDetails = NULL;
18712         savedFirst[storedGames] = firstMove;
18713         savedLast [storedGames] = lastMove;
18714         savedFramePtr[storedGames] = framePtr;
18715         framePtr -= nrMoves; // reserve space for the boards
18716         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18717             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18718             for(j=0; j<MOVE_LEN; j++)
18719                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18720             for(j=0; j<2*MOVE_LEN; j++)
18721                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18722             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18723             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18724             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18725             pvInfoList[firstMove+i-1].depth = 0;
18726             commentList[framePtr+i] = commentList[firstMove+i];
18727             commentList[firstMove+i] = NULL;
18728         }
18729
18730         storedGames++;
18731         forwardMostMove = firstMove; // truncate game so we can start variation
18732 }
18733
18734 void
18735 PushTail (int firstMove, int lastMove)
18736 {
18737         if(appData.icsActive) { // only in local mode
18738                 forwardMostMove = currentMove; // mimic old ICS behavior
18739                 return;
18740         }
18741         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18742
18743         PushInner(firstMove, lastMove);
18744         if(storedGames == 1) GreyRevert(FALSE);
18745         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18746 }
18747
18748 void
18749 PopInner (Boolean annotate)
18750 {
18751         int i, j, nrMoves;
18752         char buf[8000], moveBuf[20];
18753
18754         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18755         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18756         nrMoves = savedLast[storedGames] - currentMove;
18757         if(annotate) {
18758                 int cnt = 10;
18759                 if(!WhiteOnMove(currentMove))
18760                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18761                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18762                 for(i=currentMove; i<forwardMostMove; i++) {
18763                         if(WhiteOnMove(i))
18764                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18765                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18766                         strcat(buf, moveBuf);
18767                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18768                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18769                 }
18770                 strcat(buf, ")");
18771         }
18772         for(i=1; i<=nrMoves; i++) { // copy last variation back
18773             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18774             for(j=0; j<MOVE_LEN; j++)
18775                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18776             for(j=0; j<2*MOVE_LEN; j++)
18777                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18778             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18779             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18780             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18781             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18782             commentList[currentMove+i] = commentList[framePtr+i];
18783             commentList[framePtr+i] = NULL;
18784         }
18785         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18786         framePtr = savedFramePtr[storedGames];
18787         gameInfo.result = savedResult[storedGames];
18788         if(gameInfo.resultDetails != NULL) {
18789             free(gameInfo.resultDetails);
18790       }
18791         gameInfo.resultDetails = savedDetails[storedGames];
18792         forwardMostMove = currentMove + nrMoves;
18793 }
18794
18795 Boolean
18796 PopTail (Boolean annotate)
18797 {
18798         if(appData.icsActive) return FALSE; // only in local mode
18799         if(!storedGames) return FALSE; // sanity
18800         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18801
18802         PopInner(annotate);
18803         if(currentMove < forwardMostMove) ForwardEvent(); else
18804         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18805
18806         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18807         return TRUE;
18808 }
18809
18810 void
18811 CleanupTail ()
18812 {       // remove all shelved variations
18813         int i;
18814         for(i=0; i<storedGames; i++) {
18815             if(savedDetails[i])
18816                 free(savedDetails[i]);
18817             savedDetails[i] = NULL;
18818         }
18819         for(i=framePtr; i<MAX_MOVES; i++) {
18820                 if(commentList[i]) free(commentList[i]);
18821                 commentList[i] = NULL;
18822         }
18823         framePtr = MAX_MOVES-1;
18824         storedGames = 0;
18825 }
18826
18827 void
18828 LoadVariation (int index, char *text)
18829 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18830         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18831         int level = 0, move;
18832
18833         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18834         // first find outermost bracketing variation
18835         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18836             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18837                 if(*p == '{') wait = '}'; else
18838                 if(*p == '[') wait = ']'; else
18839                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18840                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18841             }
18842             if(*p == wait) wait = NULLCHAR; // closing ]} found
18843             p++;
18844         }
18845         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18846         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18847         end[1] = NULLCHAR; // clip off comment beyond variation
18848         ToNrEvent(currentMove-1);
18849         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18850         // kludge: use ParsePV() to append variation to game
18851         move = currentMove;
18852         ParsePV(start, TRUE, TRUE);
18853         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18854         ClearPremoveHighlights();
18855         CommentPopDown();
18856         ToNrEvent(currentMove+1);
18857 }
18858
18859 void
18860 LoadTheme ()
18861 {
18862     char *p, *q, buf[MSG_SIZ];
18863     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18864         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18865         ParseArgsFromString(buf);
18866         ActivateTheme(TRUE); // also redo colors
18867         return;
18868     }
18869     p = nickName;
18870     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18871     {
18872         int len;
18873         q = appData.themeNames;
18874         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18875       if(appData.useBitmaps) {
18876         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18877                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18878                 appData.liteBackTextureMode,
18879                 appData.darkBackTextureMode );
18880       } else {
18881         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18882                 Col2Text(2),   // lightSquareColor
18883                 Col2Text(3) ); // darkSquareColor
18884       }
18885       if(appData.useBorder) {
18886         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18887                 appData.border);
18888       } else {
18889         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18890       }
18891       if(appData.useFont) {
18892         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18893                 appData.renderPiecesWithFont,
18894                 appData.fontToPieceTable,
18895                 Col2Text(9),    // appData.fontBackColorWhite
18896                 Col2Text(10) ); // appData.fontForeColorBlack
18897       } else {
18898         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18899                 appData.pieceDirectory);
18900         if(!appData.pieceDirectory[0])
18901           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18902                 Col2Text(0),   // whitePieceColor
18903                 Col2Text(1) ); // blackPieceColor
18904       }
18905       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18906                 Col2Text(4),   // highlightSquareColor
18907                 Col2Text(5) ); // premoveHighlightColor
18908         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18909         if(insert != q) insert[-1] = NULLCHAR;
18910         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18911         if(q)   free(q);
18912     }
18913     ActivateTheme(FALSE);
18914 }