Fix default piece in Shogi promotions
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58     int flock(int f, int code);
59 #   define LOCK_EX 2
60 #   define SLASH '\\'
61
62 #   ifdef ARC_64BIT
63 #       define EGBB_NAME "egbbdll64.dll"
64 #   else
65 #       define EGBB_NAME "egbbdll.dll"
66 #   endif
67
68 #else
69
70 #   include <sys/file.h>
71 #   define SLASH '/'
72
73 #   include <dlfcn.h>
74 #   ifdef ARC_64BIT
75 #       define EGBB_NAME "egbbso64.so"
76 #   else
77 #       define EGBB_NAME "egbbso.so"
78 #   endif
79     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
80 #   define CDECL
81 #   define HMODULE void *
82 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 #   define GetProcAddress dlsym
84
85 #endif
86
87 #include "config.h"
88
89 #include <assert.h>
90 #include <stdio.h>
91 #include <ctype.h>
92 #include <errno.h>
93 #include <sys/types.h>
94 #include <sys/stat.h>
95 #include <math.h>
96 #include <ctype.h>
97
98 #if STDC_HEADERS
99 # include <stdlib.h>
100 # include <string.h>
101 # include <stdarg.h>
102 #else /* not STDC_HEADERS */
103 # if HAVE_STRING_H
104 #  include <string.h>
105 # else /* not HAVE_STRING_H */
106 #  include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
109
110 #if HAVE_SYS_FCNTL_H
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
113 # if HAVE_FCNTL_H
114 #  include <fcntl.h>
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
117
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
120 # include <time.h>
121 #else
122 # if HAVE_SYS_TIME_H
123 #  include <sys/time.h>
124 # else
125 #  include <time.h>
126 # endif
127 #endif
128
129 #if defined(_amigados) && !defined(__GNUC__)
130 struct timezone {
131     int tz_minuteswest;
132     int tz_dsttime;
133 };
134 extern int gettimeofday(struct timeval *, struct timezone *);
135 #endif
136
137 #if HAVE_UNISTD_H
138 # include <unistd.h>
139 #endif
140
141 #include "common.h"
142 #include "frontend.h"
143 #include "backend.h"
144 #include "parser.h"
145 #include "moves.h"
146 #if ZIPPY
147 # include "zippy.h"
148 #endif
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
152 #include "gettext.h"
153
154 #ifdef ENABLE_NLS
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
158 #else
159 # ifdef WIN32
160 #   define _(s) T_(s)
161 #   define N_(s) s
162 # else
163 #   define _(s) (s)
164 #   define N_(s) s
165 #   define T_(s) s
166 # endif
167 #endif
168
169
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172                          char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174                       char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187                    /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void SendToProgram P((char *message, ChessProgramState *cps));
196 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
197 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
198                            char *buf, int count, int error));
199 void SendTimeControl P((ChessProgramState *cps,
200                         int mps, long tc, int inc, int sd, int st));
201 char *TimeControlTagValue P((void));
202 void Attention P((ChessProgramState *cps));
203 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
204 int ResurrectChessProgram P((void));
205 void DisplayComment P((int moveNumber, char *text));
206 void DisplayMove P((int moveNumber));
207
208 void ParseGameHistory P((char *game));
209 void ParseBoard12 P((char *string));
210 void KeepAlive P((void));
211 void StartClocks P((void));
212 void SwitchClocks P((int nr));
213 void StopClocks P((void));
214 void ResetClocks P((void));
215 char *PGNDate P((void));
216 void SetGameInfo P((void));
217 int RegisterMove P((void));
218 void MakeRegisteredMove P((void));
219 void TruncateGame P((void));
220 int looking_at P((char *, int *, char *));
221 void CopyPlayerNameIntoFileName P((char **, char *));
222 char *SavePart P((char *));
223 int SaveGameOldStyle P((FILE *));
224 int SaveGamePGN P((FILE *));
225 int CheckFlags P((void));
226 long NextTickLength P((long));
227 void CheckTimeControl P((void));
228 void show_bytes P((FILE *, char *, int));
229 int string_to_rating P((char *str));
230 void ParseFeatures P((char* args, ChessProgramState *cps));
231 void InitBackEnd3 P((void));
232 void FeatureDone P((ChessProgramState* cps, int val));
233 void InitChessProgram P((ChessProgramState *cps, int setup));
234 void OutputKibitz(int window, char *text);
235 int PerpetualChase(int first, int last);
236 int EngineOutputIsUp();
237 void InitDrawingSizes(int x, int y);
238 void NextMatchGame P((void));
239 int NextTourneyGame P((int nr, int *swap));
240 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
241 FILE *WriteTourneyFile P((char *results, FILE *f));
242 void DisplayTwoMachinesTitle P(());
243 static void ExcludeClick P((int index));
244 void ToggleSecond P((void));
245 void PauseEngine P((ChessProgramState *cps));
246 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
247
248 #ifdef WIN32
249        extern void ConsoleCreate();
250 #endif
251
252 ChessProgramState *WhitePlayer();
253 int VerifyDisplayMode P(());
254
255 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
256 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
257 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
258 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
259 void ics_update_width P((int new_width));
260 extern char installDir[MSG_SIZ];
261 VariantClass startVariant; /* [HGM] nicks: initial variant */
262 Boolean abortMatch;
263
264 extern int tinyLayout, smallLayout;
265 ChessProgramStats programStats;
266 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
267 int endPV = -1;
268 static int exiting = 0; /* [HGM] moved to top */
269 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
270 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
271 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
272 int partnerHighlight[2];
273 Boolean partnerBoardValid = 0;
274 char partnerStatus[MSG_SIZ];
275 Boolean partnerUp;
276 Boolean originalFlip;
277 Boolean twoBoards = 0;
278 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
279 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
280 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
281 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
282 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
283 int opponentKibitzes;
284 int lastSavedGame; /* [HGM] save: ID of game */
285 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
286 extern int chatCount;
287 int chattingPartner;
288 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
289 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
290 char lastMsg[MSG_SIZ];
291 char lastTalker[MSG_SIZ];
292 ChessSquare pieceSweep = EmptySquare;
293 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
294 int promoDefaultAltered;
295 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
296 static int initPing = -1;
297 int border;       /* [HGM] width of board rim, needed to size seek graph  */
298 char bestMove[MSG_SIZ];
299 int solvingTime, totalTime;
300
301 /* States for ics_getting_history */
302 #define H_FALSE 0
303 #define H_REQUESTED 1
304 #define H_GOT_REQ_HEADER 2
305 #define H_GOT_UNREQ_HEADER 3
306 #define H_GETTING_MOVES 4
307 #define H_GOT_UNWANTED_HEADER 5
308
309 /* whosays values for GameEnds */
310 #define GE_ICS 0
311 #define GE_ENGINE 1
312 #define GE_PLAYER 2
313 #define GE_FILE 3
314 #define GE_XBOARD 4
315 #define GE_ENGINE1 5
316 #define GE_ENGINE2 6
317
318 /* Maximum number of games in a cmail message */
319 #define CMAIL_MAX_GAMES 20
320
321 /* Different types of move when calling RegisterMove */
322 #define CMAIL_MOVE   0
323 #define CMAIL_RESIGN 1
324 #define CMAIL_DRAW   2
325 #define CMAIL_ACCEPT 3
326
327 /* Different types of result to remember for each game */
328 #define CMAIL_NOT_RESULT 0
329 #define CMAIL_OLD_RESULT 1
330 #define CMAIL_NEW_RESULT 2
331
332 /* Telnet protocol constants */
333 #define TN_WILL 0373
334 #define TN_WONT 0374
335 #define TN_DO   0375
336 #define TN_DONT 0376
337 #define TN_IAC  0377
338 #define TN_ECHO 0001
339 #define TN_SGA  0003
340 #define TN_PORT 23
341
342 char*
343 safeStrCpy (char *dst, const char *src, size_t count)
344 { // [HGM] made safe
345   int i;
346   assert( dst != NULL );
347   assert( src != NULL );
348   assert( count > 0 );
349
350   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
351   if(  i == count && dst[count-1] != NULLCHAR)
352     {
353       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
354       if(appData.debugMode)
355         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
356     }
357
358   return dst;
359 }
360
361 /* Some compiler can't cast u64 to double
362  * This function do the job for us:
363
364  * We use the highest bit for cast, this only
365  * works if the highest bit is not
366  * in use (This should not happen)
367  *
368  * We used this for all compiler
369  */
370 double
371 u64ToDouble (u64 value)
372 {
373   double r;
374   u64 tmp = value & u64Const(0x7fffffffffffffff);
375   r = (double)(s64)tmp;
376   if (value & u64Const(0x8000000000000000))
377        r +=  9.2233720368547758080e18; /* 2^63 */
378  return r;
379 }
380
381 /* Fake up flags for now, as we aren't keeping track of castling
382    availability yet. [HGM] Change of logic: the flag now only
383    indicates the type of castlings allowed by the rule of the game.
384    The actual rights themselves are maintained in the array
385    castlingRights, as part of the game history, and are not probed
386    by this function.
387  */
388 int
389 PosFlags (index)
390 {
391   int flags = F_ALL_CASTLE_OK;
392   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
393   switch (gameInfo.variant) {
394   case VariantSuicide:
395     flags &= ~F_ALL_CASTLE_OK;
396   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
397     flags |= F_IGNORE_CHECK;
398   case VariantLosers:
399     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
400     break;
401   case VariantAtomic:
402     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
403     break;
404   case VariantKriegspiel:
405     flags |= F_KRIEGSPIEL_CAPTURE;
406     break;
407   case VariantCapaRandom:
408   case VariantFischeRandom:
409     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
410   case VariantNoCastle:
411   case VariantShatranj:
412   case VariantCourier:
413   case VariantMakruk:
414   case VariantASEAN:
415   case VariantGrand:
416     flags &= ~F_ALL_CASTLE_OK;
417     break;
418   case VariantChu:
419   case VariantChuChess:
420   case VariantLion:
421     flags |= F_NULL_MOVE;
422     break;
423   default:
424     break;
425   }
426   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
427   return flags;
428 }
429
430 FILE *gameFileFP, *debugFP, *serverFP;
431 char *currentDebugFile; // [HGM] debug split: to remember name
432
433 /*
434     [AS] Note: sometimes, the sscanf() function is used to parse the input
435     into a fixed-size buffer. Because of this, we must be prepared to
436     receive strings as long as the size of the input buffer, which is currently
437     set to 4K for Windows and 8K for the rest.
438     So, we must either allocate sufficiently large buffers here, or
439     reduce the size of the input buffer in the input reading part.
440 */
441
442 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
443 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
444 char thinkOutput1[MSG_SIZ*10];
445 char promoRestrict[MSG_SIZ];
446
447 ChessProgramState first, second, pairing;
448
449 /* premove variables */
450 int premoveToX = 0;
451 int premoveToY = 0;
452 int premoveFromX = 0;
453 int premoveFromY = 0;
454 int premovePromoChar = 0;
455 int gotPremove = 0;
456 Boolean alarmSounded;
457 /* end premove variables */
458
459 char *ics_prefix = "$";
460 enum ICS_TYPE ics_type = ICS_GENERIC;
461
462 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
463 int pauseExamForwardMostMove = 0;
464 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
465 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
466 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
467 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
468 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
469 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
470 int whiteFlag = FALSE, blackFlag = FALSE;
471 int userOfferedDraw = FALSE;
472 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
473 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
474 int cmailMoveType[CMAIL_MAX_GAMES];
475 long ics_clock_paused = 0;
476 ProcRef icsPR = NoProc, cmailPR = NoProc;
477 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
478 GameMode gameMode = BeginningOfGame;
479 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
480 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
481 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
482 int hiddenThinkOutputState = 0; /* [AS] */
483 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
484 int adjudicateLossPlies = 6;
485 char white_holding[64], black_holding[64];
486 TimeMark lastNodeCountTime;
487 long lastNodeCount=0;
488 int shiftKey, controlKey; // [HGM] set by mouse handler
489
490 int have_sent_ICS_logon = 0;
491 int movesPerSession;
492 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
493 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
494 Boolean adjustedClock;
495 long timeControl_2; /* [AS] Allow separate time controls */
496 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
497 long timeRemaining[2][MAX_MOVES];
498 int matchGame = 0, nextGame = 0, roundNr = 0;
499 Boolean waitingForGame = FALSE, startingEngine = FALSE;
500 TimeMark programStartTime, pauseStart;
501 char ics_handle[MSG_SIZ];
502 int have_set_title = 0;
503
504 /* animateTraining preserves the state of appData.animate
505  * when Training mode is activated. This allows the
506  * response to be animated when appData.animate == TRUE and
507  * appData.animateDragging == TRUE.
508  */
509 Boolean animateTraining;
510
511 GameInfo gameInfo;
512
513 AppData appData;
514
515 Board boards[MAX_MOVES];
516 /* [HGM] Following 7 needed for accurate legality tests: */
517 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
518 unsigned char initialRights[BOARD_FILES];
519 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
520 int   initialRulePlies, FENrulePlies;
521 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
522 int loadFlag = 0;
523 Boolean shuffleOpenings;
524 int mute; // mute all sounds
525
526 // [HGM] vari: next 12 to save and restore variations
527 #define MAX_VARIATIONS 10
528 int framePtr = MAX_MOVES-1; // points to free stack entry
529 int storedGames = 0;
530 int savedFirst[MAX_VARIATIONS];
531 int savedLast[MAX_VARIATIONS];
532 int savedFramePtr[MAX_VARIATIONS];
533 char *savedDetails[MAX_VARIATIONS];
534 ChessMove savedResult[MAX_VARIATIONS];
535
536 void PushTail P((int firstMove, int lastMove));
537 Boolean PopTail P((Boolean annotate));
538 void PushInner P((int firstMove, int lastMove));
539 void PopInner P((Boolean annotate));
540 void CleanupTail P((void));
541
542 ChessSquare  FIDEArray[2][BOARD_FILES] = {
543     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
546         BlackKing, BlackBishop, BlackKnight, BlackRook }
547 };
548
549 ChessSquare twoKingsArray[2][BOARD_FILES] = {
550     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
551         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
553         BlackKing, BlackKing, BlackKnight, BlackRook }
554 };
555
556 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
558         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
559     { BlackRook, BlackMan, BlackBishop, BlackQueen,
560         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
561 };
562
563 ChessSquare SpartanArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
565         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
566     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
567         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
568 };
569
570 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
571     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
572         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
573     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
574         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
575 };
576
577 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
578     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
579         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
580     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
581         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
582 };
583
584 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
585     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
586         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
587     { BlackRook, BlackKnight, BlackMan, BlackFerz,
588         BlackKing, BlackMan, BlackKnight, BlackRook }
589 };
590
591 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
592     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
593         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
594     { BlackRook, BlackKnight, BlackMan, BlackFerz,
595         BlackKing, BlackMan, BlackKnight, BlackRook }
596 };
597
598 ChessSquare  lionArray[2][BOARD_FILES] = {
599     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
600         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
601     { BlackRook, BlackLion, BlackBishop, BlackQueen,
602         BlackKing, BlackBishop, BlackKnight, BlackRook }
603 };
604
605
606 #if (BOARD_FILES>=10)
607 ChessSquare ShogiArray[2][BOARD_FILES] = {
608     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
609         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
610     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
611         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
612 };
613
614 ChessSquare XiangqiArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
616         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
618         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
619 };
620
621 ChessSquare CapablancaArray[2][BOARD_FILES] = {
622     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
623         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
624     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
625         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
626 };
627
628 ChessSquare GreatArray[2][BOARD_FILES] = {
629     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
630         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
631     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
632         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
633 };
634
635 ChessSquare JanusArray[2][BOARD_FILES] = {
636     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
637         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
638     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
639         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
640 };
641
642 ChessSquare GrandArray[2][BOARD_FILES] = {
643     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
644         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
645     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
646         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
647 };
648
649 ChessSquare ChuChessArray[2][BOARD_FILES] = {
650     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
651         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
652     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
653         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
654 };
655
656 #ifdef GOTHIC
657 ChessSquare GothicArray[2][BOARD_FILES] = {
658     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
659         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
660     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
661         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
662 };
663 #else // !GOTHIC
664 #define GothicArray CapablancaArray
665 #endif // !GOTHIC
666
667 #ifdef FALCON
668 ChessSquare FalconArray[2][BOARD_FILES] = {
669     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
670         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
671     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
672         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
673 };
674 #else // !FALCON
675 #define FalconArray CapablancaArray
676 #endif // !FALCON
677
678 #else // !(BOARD_FILES>=10)
679 #define XiangqiPosition FIDEArray
680 #define CapablancaArray FIDEArray
681 #define GothicArray FIDEArray
682 #define GreatArray FIDEArray
683 #endif // !(BOARD_FILES>=10)
684
685 #if (BOARD_FILES>=12)
686 ChessSquare CourierArray[2][BOARD_FILES] = {
687     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
688         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
689     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
690         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
691 };
692 ChessSquare ChuArray[6][BOARD_FILES] = {
693     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
694       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
695     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
696       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
697     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
698       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
699     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
700       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
701     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
702       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
703     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
704       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
705 };
706 #else // !(BOARD_FILES>=12)
707 #define CourierArray CapablancaArray
708 #define ChuArray CapablancaArray
709 #endif // !(BOARD_FILES>=12)
710
711
712 Board initialPosition;
713
714
715 /* Convert str to a rating. Checks for special cases of "----",
716
717    "++++", etc. Also strips ()'s */
718 int
719 string_to_rating (char *str)
720 {
721   while(*str && !isdigit(*str)) ++str;
722   if (!*str)
723     return 0;   /* One of the special "no rating" cases */
724   else
725     return atoi(str);
726 }
727
728 void
729 ClearProgramStats ()
730 {
731     /* Init programStats */
732     programStats.movelist[0] = 0;
733     programStats.depth = 0;
734     programStats.nr_moves = 0;
735     programStats.moves_left = 0;
736     programStats.nodes = 0;
737     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
738     programStats.score = 0;
739     programStats.got_only_move = 0;
740     programStats.got_fail = 0;
741     programStats.line_is_book = 0;
742 }
743
744 void
745 CommonEngineInit ()
746 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
747     if (appData.firstPlaysBlack) {
748         first.twoMachinesColor = "black\n";
749         second.twoMachinesColor = "white\n";
750     } else {
751         first.twoMachinesColor = "white\n";
752         second.twoMachinesColor = "black\n";
753     }
754
755     first.other = &second;
756     second.other = &first;
757
758     { float norm = 1;
759         if(appData.timeOddsMode) {
760             norm = appData.timeOdds[0];
761             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
762         }
763         first.timeOdds  = appData.timeOdds[0]/norm;
764         second.timeOdds = appData.timeOdds[1]/norm;
765     }
766
767     if(programVersion) free(programVersion);
768     if (appData.noChessProgram) {
769         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
770         sprintf(programVersion, "%s", PACKAGE_STRING);
771     } else {
772       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
773       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
774       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
775     }
776 }
777
778 void
779 UnloadEngine (ChessProgramState *cps)
780 {
781         /* Kill off first chess program */
782         if (cps->isr != NULL)
783           RemoveInputSource(cps->isr);
784         cps->isr = NULL;
785
786         if (cps->pr != NoProc) {
787             ExitAnalyzeMode();
788             DoSleep( appData.delayBeforeQuit );
789             SendToProgram("quit\n", cps);
790             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
791         }
792         cps->pr = NoProc;
793         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
794 }
795
796 void
797 ClearOptions (ChessProgramState *cps)
798 {
799     int i;
800     cps->nrOptions = cps->comboCnt = 0;
801     for(i=0; i<MAX_OPTIONS; i++) {
802         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
803         cps->option[i].textValue = 0;
804     }
805 }
806
807 char *engineNames[] = {
808   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
809      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
810 N_("first"),
811   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
812      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
813 N_("second")
814 };
815
816 void
817 InitEngine (ChessProgramState *cps, int n)
818 {   // [HGM] all engine initialiation put in a function that does one engine
819
820     ClearOptions(cps);
821
822     cps->which = engineNames[n];
823     cps->maybeThinking = FALSE;
824     cps->pr = NoProc;
825     cps->isr = NULL;
826     cps->sendTime = 2;
827     cps->sendDrawOffers = 1;
828
829     cps->program = appData.chessProgram[n];
830     cps->host = appData.host[n];
831     cps->dir = appData.directory[n];
832     cps->initString = appData.engInitString[n];
833     cps->computerString = appData.computerString[n];
834     cps->useSigint  = TRUE;
835     cps->useSigterm = TRUE;
836     cps->reuse = appData.reuse[n];
837     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
838     cps->useSetboard = FALSE;
839     cps->useSAN = FALSE;
840     cps->usePing = FALSE;
841     cps->lastPing = 0;
842     cps->lastPong = 0;
843     cps->usePlayother = FALSE;
844     cps->useColors = TRUE;
845     cps->useUsermove = FALSE;
846     cps->sendICS = FALSE;
847     cps->sendName = appData.icsActive;
848     cps->sdKludge = FALSE;
849     cps->stKludge = FALSE;
850     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
851     TidyProgramName(cps->program, cps->host, cps->tidy);
852     cps->matchWins = 0;
853     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
854     cps->analysisSupport = 2; /* detect */
855     cps->analyzing = FALSE;
856     cps->initDone = FALSE;
857     cps->reload = FALSE;
858     cps->pseudo = appData.pseudo[n];
859
860     /* New features added by Tord: */
861     cps->useFEN960 = FALSE;
862     cps->useOOCastle = TRUE;
863     /* End of new features added by Tord. */
864     cps->fenOverride  = appData.fenOverride[n];
865
866     /* [HGM] time odds: set factor for each machine */
867     cps->timeOdds  = appData.timeOdds[n];
868
869     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
870     cps->accumulateTC = appData.accumulateTC[n];
871     cps->maxNrOfSessions = 1;
872
873     /* [HGM] debug */
874     cps->debug = FALSE;
875
876     cps->drawDepth = appData.drawDepth[n];
877     cps->supportsNPS = UNKNOWN;
878     cps->memSize = FALSE;
879     cps->maxCores = FALSE;
880     ASSIGN(cps->egtFormats, "");
881
882     /* [HGM] options */
883     cps->optionSettings  = appData.engOptions[n];
884
885     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
886     cps->isUCI = appData.isUCI[n]; /* [AS] */
887     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
888     cps->highlight = 0;
889
890     if (appData.protocolVersion[n] > PROTOVER
891         || appData.protocolVersion[n] < 1)
892       {
893         char buf[MSG_SIZ];
894         int len;
895
896         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
897                        appData.protocolVersion[n]);
898         if( (len >= MSG_SIZ) && appData.debugMode )
899           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
900
901         DisplayFatalError(buf, 0, 2);
902       }
903     else
904       {
905         cps->protocolVersion = appData.protocolVersion[n];
906       }
907
908     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
909     ParseFeatures(appData.featureDefaults, cps);
910 }
911
912 ChessProgramState *savCps;
913
914 GameMode oldMode;
915
916 void
917 LoadEngine ()
918 {
919     int i;
920     if(WaitForEngine(savCps, LoadEngine)) return;
921     CommonEngineInit(); // recalculate time odds
922     if(gameInfo.variant != StringToVariant(appData.variant)) {
923         // we changed variant when loading the engine; this forces us to reset
924         Reset(TRUE, savCps != &first);
925         oldMode = BeginningOfGame; // to prevent restoring old mode
926     }
927     InitChessProgram(savCps, FALSE);
928     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
929     DisplayMessage("", "");
930     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
931     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
932     ThawUI();
933     SetGNUMode();
934     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
935 }
936
937 void
938 ReplaceEngine (ChessProgramState *cps, int n)
939 {
940     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
941     keepInfo = 1;
942     if(oldMode != BeginningOfGame) EditGameEvent();
943     keepInfo = 0;
944     UnloadEngine(cps);
945     appData.noChessProgram = FALSE;
946     appData.clockMode = TRUE;
947     InitEngine(cps, n);
948     UpdateLogos(TRUE);
949     if(n) return; // only startup first engine immediately; second can wait
950     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
951     LoadEngine();
952 }
953
954 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
955 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
956
957 static char resetOptions[] =
958         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
959         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
960         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
961         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
962
963 void
964 FloatToFront(char **list, char *engineLine)
965 {
966     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
967     int i=0;
968     if(appData.recentEngines <= 0) return;
969     TidyProgramName(engineLine, "localhost", tidy+1);
970     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
971     strncpy(buf+1, *list, MSG_SIZ-50);
972     if(p = strstr(buf, tidy)) { // tidy name appears in list
973         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
974         while(*p++ = *++q); // squeeze out
975     }
976     strcat(tidy, buf+1); // put list behind tidy name
977     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
978     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
979     ASSIGN(*list, tidy+1);
980 }
981
982 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
983
984 void
985 Load (ChessProgramState *cps, int i)
986 {
987     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
988     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
989         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
990         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
991         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
992         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
993         appData.firstProtocolVersion = PROTOVER;
994         ParseArgsFromString(buf);
995         SwapEngines(i);
996         ReplaceEngine(cps, i);
997         FloatToFront(&appData.recentEngineList, engineLine);
998         return;
999     }
1000     p = engineName;
1001     while(q = strchr(p, SLASH)) p = q+1;
1002     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1003     if(engineDir[0] != NULLCHAR) {
1004         ASSIGN(appData.directory[i], engineDir); p = engineName;
1005     } else if(p != engineName) { // derive directory from engine path, when not given
1006         p[-1] = 0;
1007         ASSIGN(appData.directory[i], engineName);
1008         p[-1] = SLASH;
1009         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1010     } else { ASSIGN(appData.directory[i], "."); }
1011     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1012     if(params[0]) {
1013         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1014         snprintf(command, MSG_SIZ, "%s %s", p, params);
1015         p = command;
1016     }
1017     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1018     ASSIGN(appData.chessProgram[i], p);
1019     appData.isUCI[i] = isUCI;
1020     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1021     appData.hasOwnBookUCI[i] = hasBook;
1022     if(!nickName[0]) useNick = FALSE;
1023     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1024     if(addToList) {
1025         int len;
1026         char quote;
1027         q = firstChessProgramNames;
1028         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1029         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1030         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1031                         quote, p, quote, appData.directory[i],
1032                         useNick ? " -fn \"" : "",
1033                         useNick ? nickName : "",
1034                         useNick ? "\"" : "",
1035                         v1 ? " -firstProtocolVersion 1" : "",
1036                         hasBook ? "" : " -fNoOwnBookUCI",
1037                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1038                         storeVariant ? " -variant " : "",
1039                         storeVariant ? VariantName(gameInfo.variant) : "");
1040         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1041         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1042         if(insert != q) insert[-1] = NULLCHAR;
1043         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1044         if(q)   free(q);
1045         FloatToFront(&appData.recentEngineList, buf);
1046     }
1047     ReplaceEngine(cps, i);
1048 }
1049
1050 void
1051 InitTimeControls ()
1052 {
1053     int matched, min, sec;
1054     /*
1055      * Parse timeControl resource
1056      */
1057     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1058                           appData.movesPerSession)) {
1059         char buf[MSG_SIZ];
1060         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1061         DisplayFatalError(buf, 0, 2);
1062     }
1063
1064     /*
1065      * Parse searchTime resource
1066      */
1067     if (*appData.searchTime != NULLCHAR) {
1068         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1069         if (matched == 1) {
1070             searchTime = min * 60;
1071         } else if (matched == 2) {
1072             searchTime = min * 60 + sec;
1073         } else {
1074             char buf[MSG_SIZ];
1075             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1076             DisplayFatalError(buf, 0, 2);
1077         }
1078     }
1079 }
1080
1081 void
1082 InitBackEnd1 ()
1083 {
1084
1085     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1086     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1087
1088     GetTimeMark(&programStartTime);
1089     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1090     appData.seedBase = random() + (random()<<15);
1091     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1092
1093     ClearProgramStats();
1094     programStats.ok_to_send = 1;
1095     programStats.seen_stat = 0;
1096
1097     /*
1098      * Initialize game list
1099      */
1100     ListNew(&gameList);
1101
1102
1103     /*
1104      * Internet chess server status
1105      */
1106     if (appData.icsActive) {
1107         appData.matchMode = FALSE;
1108         appData.matchGames = 0;
1109 #if ZIPPY
1110         appData.noChessProgram = !appData.zippyPlay;
1111 #else
1112         appData.zippyPlay = FALSE;
1113         appData.zippyTalk = FALSE;
1114         appData.noChessProgram = TRUE;
1115 #endif
1116         if (*appData.icsHelper != NULLCHAR) {
1117             appData.useTelnet = TRUE;
1118             appData.telnetProgram = appData.icsHelper;
1119         }
1120     } else {
1121         appData.zippyTalk = appData.zippyPlay = FALSE;
1122     }
1123
1124     /* [AS] Initialize pv info list [HGM] and game state */
1125     {
1126         int i, j;
1127
1128         for( i=0; i<=framePtr; i++ ) {
1129             pvInfoList[i].depth = -1;
1130             boards[i][EP_STATUS] = EP_NONE;
1131             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1132         }
1133     }
1134
1135     InitTimeControls();
1136
1137     /* [AS] Adjudication threshold */
1138     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1139
1140     InitEngine(&first, 0);
1141     InitEngine(&second, 1);
1142     CommonEngineInit();
1143
1144     pairing.which = "pairing"; // pairing engine
1145     pairing.pr = NoProc;
1146     pairing.isr = NULL;
1147     pairing.program = appData.pairingEngine;
1148     pairing.host = "localhost";
1149     pairing.dir = ".";
1150
1151     if (appData.icsActive) {
1152         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1153     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1154         appData.clockMode = FALSE;
1155         first.sendTime = second.sendTime = 0;
1156     }
1157
1158 #if ZIPPY
1159     /* Override some settings from environment variables, for backward
1160        compatibility.  Unfortunately it's not feasible to have the env
1161        vars just set defaults, at least in xboard.  Ugh.
1162     */
1163     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1164       ZippyInit();
1165     }
1166 #endif
1167
1168     if (!appData.icsActive) {
1169       char buf[MSG_SIZ];
1170       int len;
1171
1172       /* Check for variants that are supported only in ICS mode,
1173          or not at all.  Some that are accepted here nevertheless
1174          have bugs; see comments below.
1175       */
1176       VariantClass variant = StringToVariant(appData.variant);
1177       switch (variant) {
1178       case VariantBughouse:     /* need four players and two boards */
1179       case VariantKriegspiel:   /* need to hide pieces and move details */
1180         /* case VariantFischeRandom: (Fabien: moved below) */
1181         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1182         if( (len >= MSG_SIZ) && appData.debugMode )
1183           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1184
1185         DisplayFatalError(buf, 0, 2);
1186         return;
1187
1188       case VariantUnknown:
1189       case VariantLoadable:
1190       case Variant29:
1191       case Variant30:
1192       case Variant31:
1193       case Variant32:
1194       case Variant33:
1195       case Variant34:
1196       case Variant35:
1197       case Variant36:
1198       default:
1199         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1200         if( (len >= MSG_SIZ) && appData.debugMode )
1201           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1202
1203         DisplayFatalError(buf, 0, 2);
1204         return;
1205
1206       case VariantNormal:     /* definitely works! */
1207         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1208           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1209           return;
1210         }
1211       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1212       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1213       case VariantGothic:     /* [HGM] should work */
1214       case VariantCapablanca: /* [HGM] should work */
1215       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1216       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1217       case VariantChu:        /* [HGM] experimental */
1218       case VariantKnightmate: /* [HGM] should work */
1219       case VariantCylinder:   /* [HGM] untested */
1220       case VariantFalcon:     /* [HGM] untested */
1221       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1222                                  offboard interposition not understood */
1223       case VariantWildCastle: /* pieces not automatically shuffled */
1224       case VariantNoCastle:   /* pieces not automatically shuffled */
1225       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1226       case VariantLosers:     /* should work except for win condition,
1227                                  and doesn't know captures are mandatory */
1228       case VariantSuicide:    /* should work except for win condition,
1229                                  and doesn't know captures are mandatory */
1230       case VariantGiveaway:   /* should work except for win condition,
1231                                  and doesn't know captures are mandatory */
1232       case VariantTwoKings:   /* should work */
1233       case VariantAtomic:     /* should work except for win condition */
1234       case Variant3Check:     /* should work except for win condition */
1235       case VariantShatranj:   /* should work except for all win conditions */
1236       case VariantMakruk:     /* should work except for draw countdown */
1237       case VariantASEAN :     /* should work except for draw countdown */
1238       case VariantBerolina:   /* might work if TestLegality is off */
1239       case VariantCapaRandom: /* should work */
1240       case VariantJanus:      /* should work */
1241       case VariantSuper:      /* experimental */
1242       case VariantGreat:      /* experimental, requires legality testing to be off */
1243       case VariantSChess:     /* S-Chess, should work */
1244       case VariantGrand:      /* should work */
1245       case VariantSpartan:    /* should work */
1246       case VariantLion:       /* should work */
1247       case VariantChuChess:   /* should work */
1248         break;
1249       }
1250     }
1251
1252 }
1253
1254 int
1255 NextIntegerFromString (char ** str, long * value)
1256 {
1257     int result = -1;
1258     char * s = *str;
1259
1260     while( *s == ' ' || *s == '\t' ) {
1261         s++;
1262     }
1263
1264     *value = 0;
1265
1266     if( *s >= '0' && *s <= '9' ) {
1267         while( *s >= '0' && *s <= '9' ) {
1268             *value = *value * 10 + (*s - '0');
1269             s++;
1270         }
1271
1272         result = 0;
1273     }
1274
1275     *str = s;
1276
1277     return result;
1278 }
1279
1280 int
1281 NextTimeControlFromString (char ** str, long * value)
1282 {
1283     long temp;
1284     int result = NextIntegerFromString( str, &temp );
1285
1286     if( result == 0 ) {
1287         *value = temp * 60; /* Minutes */
1288         if( **str == ':' ) {
1289             (*str)++;
1290             result = NextIntegerFromString( str, &temp );
1291             *value += temp; /* Seconds */
1292         }
1293     }
1294
1295     return result;
1296 }
1297
1298 int
1299 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1300 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1301     int result = -1, type = 0; long temp, temp2;
1302
1303     if(**str != ':') return -1; // old params remain in force!
1304     (*str)++;
1305     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1306     if( NextIntegerFromString( str, &temp ) ) return -1;
1307     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1308
1309     if(**str != '/') {
1310         /* time only: incremental or sudden-death time control */
1311         if(**str == '+') { /* increment follows; read it */
1312             (*str)++;
1313             if(**str == '!') type = *(*str)++; // Bronstein TC
1314             if(result = NextIntegerFromString( str, &temp2)) return -1;
1315             *inc = temp2 * 1000;
1316             if(**str == '.') { // read fraction of increment
1317                 char *start = ++(*str);
1318                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1319                 temp2 *= 1000;
1320                 while(start++ < *str) temp2 /= 10;
1321                 *inc += temp2;
1322             }
1323         } else *inc = 0;
1324         *moves = 0; *tc = temp * 1000; *incType = type;
1325         return 0;
1326     }
1327
1328     (*str)++; /* classical time control */
1329     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1330
1331     if(result == 0) {
1332         *moves = temp;
1333         *tc    = temp2 * 1000;
1334         *inc   = 0;
1335         *incType = type;
1336     }
1337     return result;
1338 }
1339
1340 int
1341 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1342 {   /* [HGM] get time to add from the multi-session time-control string */
1343     int incType, moves=1; /* kludge to force reading of first session */
1344     long time, increment;
1345     char *s = tcString;
1346
1347     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1348     do {
1349         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1350         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1351         if(movenr == -1) return time;    /* last move before new session     */
1352         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1353         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1354         if(!moves) return increment;     /* current session is incremental   */
1355         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1356     } while(movenr >= -1);               /* try again for next session       */
1357
1358     return 0; // no new time quota on this move
1359 }
1360
1361 int
1362 ParseTimeControl (char *tc, float ti, int mps)
1363 {
1364   long tc1;
1365   long tc2;
1366   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1367   int min, sec=0;
1368
1369   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1370   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1371       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1372   if(ti > 0) {
1373
1374     if(mps)
1375       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1376     else
1377       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1378   } else {
1379     if(mps)
1380       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1381     else
1382       snprintf(buf, MSG_SIZ, ":%s", mytc);
1383   }
1384   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1385
1386   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1387     return FALSE;
1388   }
1389
1390   if( *tc == '/' ) {
1391     /* Parse second time control */
1392     tc++;
1393
1394     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1395       return FALSE;
1396     }
1397
1398     if( tc2 == 0 ) {
1399       return FALSE;
1400     }
1401
1402     timeControl_2 = tc2 * 1000;
1403   }
1404   else {
1405     timeControl_2 = 0;
1406   }
1407
1408   if( tc1 == 0 ) {
1409     return FALSE;
1410   }
1411
1412   timeControl = tc1 * 1000;
1413
1414   if (ti >= 0) {
1415     timeIncrement = ti * 1000;  /* convert to ms */
1416     movesPerSession = 0;
1417   } else {
1418     timeIncrement = 0;
1419     movesPerSession = mps;
1420   }
1421   return TRUE;
1422 }
1423
1424 void
1425 InitBackEnd2 ()
1426 {
1427     if (appData.debugMode) {
1428 #    ifdef __GIT_VERSION
1429       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1430 #    else
1431       fprintf(debugFP, "Version: %s\n", programVersion);
1432 #    endif
1433     }
1434     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1435
1436     set_cont_sequence(appData.wrapContSeq);
1437     if (appData.matchGames > 0) {
1438         appData.matchMode = TRUE;
1439     } else if (appData.matchMode) {
1440         appData.matchGames = 1;
1441     }
1442     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1443         appData.matchGames = appData.sameColorGames;
1444     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1445         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1446         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1447     }
1448     Reset(TRUE, FALSE);
1449     if (appData.noChessProgram || first.protocolVersion == 1) {
1450       InitBackEnd3();
1451     } else {
1452       /* kludge: allow timeout for initial "feature" commands */
1453       FreezeUI();
1454       DisplayMessage("", _("Starting chess program"));
1455       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1456     }
1457 }
1458
1459 int
1460 CalculateIndex (int index, int gameNr)
1461 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1462     int res;
1463     if(index > 0) return index; // fixed nmber
1464     if(index == 0) return 1;
1465     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1466     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1467     return res;
1468 }
1469
1470 int
1471 LoadGameOrPosition (int gameNr)
1472 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1473     if (*appData.loadGameFile != NULLCHAR) {
1474         if (!LoadGameFromFile(appData.loadGameFile,
1475                 CalculateIndex(appData.loadGameIndex, gameNr),
1476                               appData.loadGameFile, FALSE)) {
1477             DisplayFatalError(_("Bad game file"), 0, 1);
1478             return 0;
1479         }
1480     } else if (*appData.loadPositionFile != NULLCHAR) {
1481         if (!LoadPositionFromFile(appData.loadPositionFile,
1482                 CalculateIndex(appData.loadPositionIndex, gameNr),
1483                                   appData.loadPositionFile)) {
1484             DisplayFatalError(_("Bad position file"), 0, 1);
1485             return 0;
1486         }
1487     }
1488     return 1;
1489 }
1490
1491 void
1492 ReserveGame (int gameNr, char resChar)
1493 {
1494     FILE *tf = fopen(appData.tourneyFile, "r+");
1495     char *p, *q, c, buf[MSG_SIZ];
1496     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1497     safeStrCpy(buf, lastMsg, MSG_SIZ);
1498     DisplayMessage(_("Pick new game"), "");
1499     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1500     ParseArgsFromFile(tf);
1501     p = q = appData.results;
1502     if(appData.debugMode) {
1503       char *r = appData.participants;
1504       fprintf(debugFP, "results = '%s'\n", p);
1505       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1506       fprintf(debugFP, "\n");
1507     }
1508     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1509     nextGame = q - p;
1510     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1511     safeStrCpy(q, p, strlen(p) + 2);
1512     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1513     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1514     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1515         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1516         q[nextGame] = '*';
1517     }
1518     fseek(tf, -(strlen(p)+4), SEEK_END);
1519     c = fgetc(tf);
1520     if(c != '"') // depending on DOS or Unix line endings we can be one off
1521          fseek(tf, -(strlen(p)+2), SEEK_END);
1522     else fseek(tf, -(strlen(p)+3), SEEK_END);
1523     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1524     DisplayMessage(buf, "");
1525     free(p); appData.results = q;
1526     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1527        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1528       int round = appData.defaultMatchGames * appData.tourneyType;
1529       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1530          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1531         UnloadEngine(&first);  // next game belongs to other pairing;
1532         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1533     }
1534     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1535 }
1536
1537 void
1538 MatchEvent (int mode)
1539 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1540         int dummy;
1541         if(matchMode) { // already in match mode: switch it off
1542             abortMatch = TRUE;
1543             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1544             return;
1545         }
1546 //      if(gameMode != BeginningOfGame) {
1547 //          DisplayError(_("You can only start a match from the initial position."), 0);
1548 //          return;
1549 //      }
1550         abortMatch = FALSE;
1551         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1552         /* Set up machine vs. machine match */
1553         nextGame = 0;
1554         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1555         if(appData.tourneyFile[0]) {
1556             ReserveGame(-1, 0);
1557             if(nextGame > appData.matchGames) {
1558                 char buf[MSG_SIZ];
1559                 if(strchr(appData.results, '*') == NULL) {
1560                     FILE *f;
1561                     appData.tourneyCycles++;
1562                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1563                         fclose(f);
1564                         NextTourneyGame(-1, &dummy);
1565                         ReserveGame(-1, 0);
1566                         if(nextGame <= appData.matchGames) {
1567                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1568                             matchMode = mode;
1569                             ScheduleDelayedEvent(NextMatchGame, 10000);
1570                             return;
1571                         }
1572                     }
1573                 }
1574                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1575                 DisplayError(buf, 0);
1576                 appData.tourneyFile[0] = 0;
1577                 return;
1578             }
1579         } else
1580         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1581             DisplayFatalError(_("Can't have a match with no chess programs"),
1582                               0, 2);
1583             return;
1584         }
1585         matchMode = mode;
1586         matchGame = roundNr = 1;
1587         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1588         NextMatchGame();
1589 }
1590
1591 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1592
1593 void
1594 InitBackEnd3 P((void))
1595 {
1596     GameMode initialMode;
1597     char buf[MSG_SIZ];
1598     int err, len;
1599
1600     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1601        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1602         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1603        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1604        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1605         char c, *q = first.variants, *p = strchr(q, ',');
1606         if(p) *p = NULLCHAR;
1607         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1608             int w, h, s;
1609             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1610                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1611             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1612             Reset(TRUE, FALSE);         // and re-initialize
1613         }
1614         if(p) *p = ',';
1615     }
1616
1617     InitChessProgram(&first, startedFromSetupPosition);
1618
1619     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1620         free(programVersion);
1621         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1622         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1623         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1624     }
1625
1626     if (appData.icsActive) {
1627 #ifdef WIN32
1628         /* [DM] Make a console window if needed [HGM] merged ifs */
1629         ConsoleCreate();
1630 #endif
1631         err = establish();
1632         if (err != 0)
1633           {
1634             if (*appData.icsCommPort != NULLCHAR)
1635               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1636                              appData.icsCommPort);
1637             else
1638               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1639                         appData.icsHost, appData.icsPort);
1640
1641             if( (len >= MSG_SIZ) && appData.debugMode )
1642               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1643
1644             DisplayFatalError(buf, err, 1);
1645             return;
1646         }
1647         SetICSMode();
1648         telnetISR =
1649           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1650         fromUserISR =
1651           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1652         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1653             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1654     } else if (appData.noChessProgram) {
1655         SetNCPMode();
1656     } else {
1657         SetGNUMode();
1658     }
1659
1660     if (*appData.cmailGameName != NULLCHAR) {
1661         SetCmailMode();
1662         OpenLoopback(&cmailPR);
1663         cmailISR =
1664           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1665     }
1666
1667     ThawUI();
1668     DisplayMessage("", "");
1669     if (StrCaseCmp(appData.initialMode, "") == 0) {
1670       initialMode = BeginningOfGame;
1671       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1672         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1673         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1674         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1675         ModeHighlight();
1676       }
1677     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1678       initialMode = TwoMachinesPlay;
1679     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1680       initialMode = AnalyzeFile;
1681     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1682       initialMode = AnalyzeMode;
1683     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1684       initialMode = MachinePlaysWhite;
1685     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1686       initialMode = MachinePlaysBlack;
1687     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1688       initialMode = EditGame;
1689     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1690       initialMode = EditPosition;
1691     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1692       initialMode = Training;
1693     } else {
1694       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1695       if( (len >= MSG_SIZ) && appData.debugMode )
1696         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1697
1698       DisplayFatalError(buf, 0, 2);
1699       return;
1700     }
1701
1702     if (appData.matchMode) {
1703         if(appData.tourneyFile[0]) { // start tourney from command line
1704             FILE *f;
1705             if(f = fopen(appData.tourneyFile, "r")) {
1706                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1707                 fclose(f);
1708                 appData.clockMode = TRUE;
1709                 SetGNUMode();
1710             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1711         }
1712         MatchEvent(TRUE);
1713     } else if (*appData.cmailGameName != NULLCHAR) {
1714         /* Set up cmail mode */
1715         ReloadCmailMsgEvent(TRUE);
1716     } else {
1717         /* Set up other modes */
1718         if (initialMode == AnalyzeFile) {
1719           if (*appData.loadGameFile == NULLCHAR) {
1720             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1721             return;
1722           }
1723         }
1724         if (*appData.loadGameFile != NULLCHAR) {
1725             (void) LoadGameFromFile(appData.loadGameFile,
1726                                     appData.loadGameIndex,
1727                                     appData.loadGameFile, TRUE);
1728         } else if (*appData.loadPositionFile != NULLCHAR) {
1729             (void) LoadPositionFromFile(appData.loadPositionFile,
1730                                         appData.loadPositionIndex,
1731                                         appData.loadPositionFile);
1732             /* [HGM] try to make self-starting even after FEN load */
1733             /* to allow automatic setup of fairy variants with wtm */
1734             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1735                 gameMode = BeginningOfGame;
1736                 setboardSpoiledMachineBlack = 1;
1737             }
1738             /* [HGM] loadPos: make that every new game uses the setup */
1739             /* from file as long as we do not switch variant          */
1740             if(!blackPlaysFirst) {
1741                 startedFromPositionFile = TRUE;
1742                 CopyBoard(filePosition, boards[0]);
1743                 CopyBoard(initialPosition, boards[0]);
1744             }
1745         }
1746         if (initialMode == AnalyzeMode) {
1747           if (appData.noChessProgram) {
1748             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1749             return;
1750           }
1751           if (appData.icsActive) {
1752             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1753             return;
1754           }
1755           AnalyzeModeEvent();
1756         } else if (initialMode == AnalyzeFile) {
1757           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1758           ShowThinkingEvent();
1759           AnalyzeFileEvent();
1760           AnalysisPeriodicEvent(1);
1761         } else if (initialMode == MachinePlaysWhite) {
1762           if (appData.noChessProgram) {
1763             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1764                               0, 2);
1765             return;
1766           }
1767           if (appData.icsActive) {
1768             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1769                               0, 2);
1770             return;
1771           }
1772           MachineWhiteEvent();
1773         } else if (initialMode == MachinePlaysBlack) {
1774           if (appData.noChessProgram) {
1775             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1776                               0, 2);
1777             return;
1778           }
1779           if (appData.icsActive) {
1780             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1781                               0, 2);
1782             return;
1783           }
1784           MachineBlackEvent();
1785         } else if (initialMode == TwoMachinesPlay) {
1786           if (appData.noChessProgram) {
1787             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1788                               0, 2);
1789             return;
1790           }
1791           if (appData.icsActive) {
1792             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1793                               0, 2);
1794             return;
1795           }
1796           TwoMachinesEvent();
1797         } else if (initialMode == EditGame) {
1798           EditGameEvent();
1799         } else if (initialMode == EditPosition) {
1800           EditPositionEvent();
1801         } else if (initialMode == Training) {
1802           if (*appData.loadGameFile == NULLCHAR) {
1803             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1804             return;
1805           }
1806           TrainingEvent();
1807         }
1808     }
1809 }
1810
1811 void
1812 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1813 {
1814     DisplayBook(current+1);
1815
1816     MoveHistorySet( movelist, first, last, current, pvInfoList );
1817
1818     EvalGraphSet( first, last, current, pvInfoList );
1819
1820     MakeEngineOutputTitle();
1821 }
1822
1823 /*
1824  * Establish will establish a contact to a remote host.port.
1825  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1826  *  used to talk to the host.
1827  * Returns 0 if okay, error code if not.
1828  */
1829 int
1830 establish ()
1831 {
1832     char buf[MSG_SIZ];
1833
1834     if (*appData.icsCommPort != NULLCHAR) {
1835         /* Talk to the host through a serial comm port */
1836         return OpenCommPort(appData.icsCommPort, &icsPR);
1837
1838     } else if (*appData.gateway != NULLCHAR) {
1839         if (*appData.remoteShell == NULLCHAR) {
1840             /* Use the rcmd protocol to run telnet program on a gateway host */
1841             snprintf(buf, sizeof(buf), "%s %s %s",
1842                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1843             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1844
1845         } else {
1846             /* Use the rsh program to run telnet program on a gateway host */
1847             if (*appData.remoteUser == NULLCHAR) {
1848                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1849                         appData.gateway, appData.telnetProgram,
1850                         appData.icsHost, appData.icsPort);
1851             } else {
1852                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1853                         appData.remoteShell, appData.gateway,
1854                         appData.remoteUser, appData.telnetProgram,
1855                         appData.icsHost, appData.icsPort);
1856             }
1857             return StartChildProcess(buf, "", &icsPR);
1858
1859         }
1860     } else if (appData.useTelnet) {
1861         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1862
1863     } else {
1864         /* TCP socket interface differs somewhat between
1865            Unix and NT; handle details in the front end.
1866            */
1867         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1868     }
1869 }
1870
1871 void
1872 EscapeExpand (char *p, char *q)
1873 {       // [HGM] initstring: routine to shape up string arguments
1874         while(*p++ = *q++) if(p[-1] == '\\')
1875             switch(*q++) {
1876                 case 'n': p[-1] = '\n'; break;
1877                 case 'r': p[-1] = '\r'; break;
1878                 case 't': p[-1] = '\t'; break;
1879                 case '\\': p[-1] = '\\'; break;
1880                 case 0: *p = 0; return;
1881                 default: p[-1] = q[-1]; break;
1882             }
1883 }
1884
1885 void
1886 show_bytes (FILE *fp, char *buf, int count)
1887 {
1888     while (count--) {
1889         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1890             fprintf(fp, "\\%03o", *buf & 0xff);
1891         } else {
1892             putc(*buf, fp);
1893         }
1894         buf++;
1895     }
1896     fflush(fp);
1897 }
1898
1899 /* Returns an errno value */
1900 int
1901 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1902 {
1903     char buf[8192], *p, *q, *buflim;
1904     int left, newcount, outcount;
1905
1906     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1907         *appData.gateway != NULLCHAR) {
1908         if (appData.debugMode) {
1909             fprintf(debugFP, ">ICS: ");
1910             show_bytes(debugFP, message, count);
1911             fprintf(debugFP, "\n");
1912         }
1913         return OutputToProcess(pr, message, count, outError);
1914     }
1915
1916     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1917     p = message;
1918     q = buf;
1919     left = count;
1920     newcount = 0;
1921     while (left) {
1922         if (q >= buflim) {
1923             if (appData.debugMode) {
1924                 fprintf(debugFP, ">ICS: ");
1925                 show_bytes(debugFP, buf, newcount);
1926                 fprintf(debugFP, "\n");
1927             }
1928             outcount = OutputToProcess(pr, buf, newcount, outError);
1929             if (outcount < newcount) return -1; /* to be sure */
1930             q = buf;
1931             newcount = 0;
1932         }
1933         if (*p == '\n') {
1934             *q++ = '\r';
1935             newcount++;
1936         } else if (((unsigned char) *p) == TN_IAC) {
1937             *q++ = (char) TN_IAC;
1938             newcount ++;
1939         }
1940         *q++ = *p++;
1941         newcount++;
1942         left--;
1943     }
1944     if (appData.debugMode) {
1945         fprintf(debugFP, ">ICS: ");
1946         show_bytes(debugFP, buf, newcount);
1947         fprintf(debugFP, "\n");
1948     }
1949     outcount = OutputToProcess(pr, buf, newcount, outError);
1950     if (outcount < newcount) return -1; /* to be sure */
1951     return count;
1952 }
1953
1954 void
1955 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1956 {
1957     int outError, outCount;
1958     static int gotEof = 0;
1959     static FILE *ini;
1960
1961     /* Pass data read from player on to ICS */
1962     if (count > 0) {
1963         gotEof = 0;
1964         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1965         if (outCount < count) {
1966             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1967         }
1968         if(have_sent_ICS_logon == 2) {
1969           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1970             fprintf(ini, "%s", message);
1971             have_sent_ICS_logon = 3;
1972           } else
1973             have_sent_ICS_logon = 1;
1974         } else if(have_sent_ICS_logon == 3) {
1975             fprintf(ini, "%s", message);
1976             fclose(ini);
1977           have_sent_ICS_logon = 1;
1978         }
1979     } else if (count < 0) {
1980         RemoveInputSource(isr);
1981         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1982     } else if (gotEof++ > 0) {
1983         RemoveInputSource(isr);
1984         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1985     }
1986 }
1987
1988 void
1989 KeepAlive ()
1990 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1991     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1992     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1993     SendToICS("date\n");
1994     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1995 }
1996
1997 /* added routine for printf style output to ics */
1998 void
1999 ics_printf (char *format, ...)
2000 {
2001     char buffer[MSG_SIZ];
2002     va_list args;
2003
2004     va_start(args, format);
2005     vsnprintf(buffer, sizeof(buffer), format, args);
2006     buffer[sizeof(buffer)-1] = '\0';
2007     SendToICS(buffer);
2008     va_end(args);
2009 }
2010
2011 void
2012 SendToICS (char *s)
2013 {
2014     int count, outCount, outError;
2015
2016     if (icsPR == NoProc) return;
2017
2018     count = strlen(s);
2019     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2020     if (outCount < count) {
2021         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2022     }
2023 }
2024
2025 /* This is used for sending logon scripts to the ICS. Sending
2026    without a delay causes problems when using timestamp on ICC
2027    (at least on my machine). */
2028 void
2029 SendToICSDelayed (char *s, long msdelay)
2030 {
2031     int count, outCount, outError;
2032
2033     if (icsPR == NoProc) return;
2034
2035     count = strlen(s);
2036     if (appData.debugMode) {
2037         fprintf(debugFP, ">ICS: ");
2038         show_bytes(debugFP, s, count);
2039         fprintf(debugFP, "\n");
2040     }
2041     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2042                                       msdelay);
2043     if (outCount < count) {
2044         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2045     }
2046 }
2047
2048
2049 /* Remove all highlighting escape sequences in s
2050    Also deletes any suffix starting with '('
2051    */
2052 char *
2053 StripHighlightAndTitle (char *s)
2054 {
2055     static char retbuf[MSG_SIZ];
2056     char *p = retbuf;
2057
2058     while (*s != NULLCHAR) {
2059         while (*s == '\033') {
2060             while (*s != NULLCHAR && !isalpha(*s)) s++;
2061             if (*s != NULLCHAR) s++;
2062         }
2063         while (*s != NULLCHAR && *s != '\033') {
2064             if (*s == '(' || *s == '[') {
2065                 *p = NULLCHAR;
2066                 return retbuf;
2067             }
2068             *p++ = *s++;
2069         }
2070     }
2071     *p = NULLCHAR;
2072     return retbuf;
2073 }
2074
2075 /* Remove all highlighting escape sequences in s */
2076 char *
2077 StripHighlight (char *s)
2078 {
2079     static char retbuf[MSG_SIZ];
2080     char *p = retbuf;
2081
2082     while (*s != NULLCHAR) {
2083         while (*s == '\033') {
2084             while (*s != NULLCHAR && !isalpha(*s)) s++;
2085             if (*s != NULLCHAR) s++;
2086         }
2087         while (*s != NULLCHAR && *s != '\033') {
2088             *p++ = *s++;
2089         }
2090     }
2091     *p = NULLCHAR;
2092     return retbuf;
2093 }
2094
2095 char engineVariant[MSG_SIZ];
2096 char *variantNames[] = VARIANT_NAMES;
2097 char *
2098 VariantName (VariantClass v)
2099 {
2100     if(v == VariantUnknown || *engineVariant) return engineVariant;
2101     return variantNames[v];
2102 }
2103
2104
2105 /* Identify a variant from the strings the chess servers use or the
2106    PGN Variant tag names we use. */
2107 VariantClass
2108 StringToVariant (char *e)
2109 {
2110     char *p;
2111     int wnum = -1;
2112     VariantClass v = VariantNormal;
2113     int i, found = FALSE;
2114     char buf[MSG_SIZ], c;
2115     int len;
2116
2117     if (!e) return v;
2118
2119     /* [HGM] skip over optional board-size prefixes */
2120     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2121         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2122         while( *e++ != '_');
2123     }
2124
2125     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2126         v = VariantNormal;
2127         found = TRUE;
2128     } else
2129     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2130       if (p = StrCaseStr(e, variantNames[i])) {
2131         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2132         v = (VariantClass) i;
2133         found = TRUE;
2134         break;
2135       }
2136     }
2137
2138     if (!found) {
2139       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2140           || StrCaseStr(e, "wild/fr")
2141           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2142         v = VariantFischeRandom;
2143       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2144                  (i = 1, p = StrCaseStr(e, "w"))) {
2145         p += i;
2146         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2147         if (isdigit(*p)) {
2148           wnum = atoi(p);
2149         } else {
2150           wnum = -1;
2151         }
2152         switch (wnum) {
2153         case 0: /* FICS only, actually */
2154         case 1:
2155           /* Castling legal even if K starts on d-file */
2156           v = VariantWildCastle;
2157           break;
2158         case 2:
2159         case 3:
2160         case 4:
2161           /* Castling illegal even if K & R happen to start in
2162              normal positions. */
2163           v = VariantNoCastle;
2164           break;
2165         case 5:
2166         case 7:
2167         case 8:
2168         case 10:
2169         case 11:
2170         case 12:
2171         case 13:
2172         case 14:
2173         case 15:
2174         case 18:
2175         case 19:
2176           /* Castling legal iff K & R start in normal positions */
2177           v = VariantNormal;
2178           break;
2179         case 6:
2180         case 20:
2181         case 21:
2182           /* Special wilds for position setup; unclear what to do here */
2183           v = VariantLoadable;
2184           break;
2185         case 9:
2186           /* Bizarre ICC game */
2187           v = VariantTwoKings;
2188           break;
2189         case 16:
2190           v = VariantKriegspiel;
2191           break;
2192         case 17:
2193           v = VariantLosers;
2194           break;
2195         case 22:
2196           v = VariantFischeRandom;
2197           break;
2198         case 23:
2199           v = VariantCrazyhouse;
2200           break;
2201         case 24:
2202           v = VariantBughouse;
2203           break;
2204         case 25:
2205           v = Variant3Check;
2206           break;
2207         case 26:
2208           /* Not quite the same as FICS suicide! */
2209           v = VariantGiveaway;
2210           break;
2211         case 27:
2212           v = VariantAtomic;
2213           break;
2214         case 28:
2215           v = VariantShatranj;
2216           break;
2217
2218         /* Temporary names for future ICC types.  The name *will* change in
2219            the next xboard/WinBoard release after ICC defines it. */
2220         case 29:
2221           v = Variant29;
2222           break;
2223         case 30:
2224           v = Variant30;
2225           break;
2226         case 31:
2227           v = Variant31;
2228           break;
2229         case 32:
2230           v = Variant32;
2231           break;
2232         case 33:
2233           v = Variant33;
2234           break;
2235         case 34:
2236           v = Variant34;
2237           break;
2238         case 35:
2239           v = Variant35;
2240           break;
2241         case 36:
2242           v = Variant36;
2243           break;
2244         case 37:
2245           v = VariantShogi;
2246           break;
2247         case 38:
2248           v = VariantXiangqi;
2249           break;
2250         case 39:
2251           v = VariantCourier;
2252           break;
2253         case 40:
2254           v = VariantGothic;
2255           break;
2256         case 41:
2257           v = VariantCapablanca;
2258           break;
2259         case 42:
2260           v = VariantKnightmate;
2261           break;
2262         case 43:
2263           v = VariantFairy;
2264           break;
2265         case 44:
2266           v = VariantCylinder;
2267           break;
2268         case 45:
2269           v = VariantFalcon;
2270           break;
2271         case 46:
2272           v = VariantCapaRandom;
2273           break;
2274         case 47:
2275           v = VariantBerolina;
2276           break;
2277         case 48:
2278           v = VariantJanus;
2279           break;
2280         case 49:
2281           v = VariantSuper;
2282           break;
2283         case 50:
2284           v = VariantGreat;
2285           break;
2286         case -1:
2287           /* Found "wild" or "w" in the string but no number;
2288              must assume it's normal chess. */
2289           v = VariantNormal;
2290           break;
2291         default:
2292           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2293           if( (len >= MSG_SIZ) && appData.debugMode )
2294             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2295
2296           DisplayError(buf, 0);
2297           v = VariantUnknown;
2298           break;
2299         }
2300       }
2301     }
2302     if (appData.debugMode) {
2303       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2304               e, wnum, VariantName(v));
2305     }
2306     return v;
2307 }
2308
2309 static int leftover_start = 0, leftover_len = 0;
2310 char star_match[STAR_MATCH_N][MSG_SIZ];
2311
2312 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2313    advance *index beyond it, and set leftover_start to the new value of
2314    *index; else return FALSE.  If pattern contains the character '*', it
2315    matches any sequence of characters not containing '\r', '\n', or the
2316    character following the '*' (if any), and the matched sequence(s) are
2317    copied into star_match.
2318    */
2319 int
2320 looking_at ( char *buf, int *index, char *pattern)
2321 {
2322     char *bufp = &buf[*index], *patternp = pattern;
2323     int star_count = 0;
2324     char *matchp = star_match[0];
2325
2326     for (;;) {
2327         if (*patternp == NULLCHAR) {
2328             *index = leftover_start = bufp - buf;
2329             *matchp = NULLCHAR;
2330             return TRUE;
2331         }
2332         if (*bufp == NULLCHAR) return FALSE;
2333         if (*patternp == '*') {
2334             if (*bufp == *(patternp + 1)) {
2335                 *matchp = NULLCHAR;
2336                 matchp = star_match[++star_count];
2337                 patternp += 2;
2338                 bufp++;
2339                 continue;
2340             } else if (*bufp == '\n' || *bufp == '\r') {
2341                 patternp++;
2342                 if (*patternp == NULLCHAR)
2343                   continue;
2344                 else
2345                   return FALSE;
2346             } else {
2347                 *matchp++ = *bufp++;
2348                 continue;
2349             }
2350         }
2351         if (*patternp != *bufp) return FALSE;
2352         patternp++;
2353         bufp++;
2354     }
2355 }
2356
2357 void
2358 SendToPlayer (char *data, int length)
2359 {
2360     int error, outCount;
2361     outCount = OutputToProcess(NoProc, data, length, &error);
2362     if (outCount < length) {
2363         DisplayFatalError(_("Error writing to display"), error, 1);
2364     }
2365 }
2366
2367 void
2368 PackHolding (char packed[], char *holding)
2369 {
2370     char *p = holding;
2371     char *q = packed;
2372     int runlength = 0;
2373     int curr = 9999;
2374     do {
2375         if (*p == curr) {
2376             runlength++;
2377         } else {
2378             switch (runlength) {
2379               case 0:
2380                 break;
2381               case 1:
2382                 *q++ = curr;
2383                 break;
2384               case 2:
2385                 *q++ = curr;
2386                 *q++ = curr;
2387                 break;
2388               default:
2389                 sprintf(q, "%d", runlength);
2390                 while (*q) q++;
2391                 *q++ = curr;
2392                 break;
2393             }
2394             runlength = 1;
2395             curr = *p;
2396         }
2397     } while (*p++);
2398     *q = NULLCHAR;
2399 }
2400
2401 /* Telnet protocol requests from the front end */
2402 void
2403 TelnetRequest (unsigned char ddww, unsigned char option)
2404 {
2405     unsigned char msg[3];
2406     int outCount, outError;
2407
2408     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2409
2410     if (appData.debugMode) {
2411         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2412         switch (ddww) {
2413           case TN_DO:
2414             ddwwStr = "DO";
2415             break;
2416           case TN_DONT:
2417             ddwwStr = "DONT";
2418             break;
2419           case TN_WILL:
2420             ddwwStr = "WILL";
2421             break;
2422           case TN_WONT:
2423             ddwwStr = "WONT";
2424             break;
2425           default:
2426             ddwwStr = buf1;
2427             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2428             break;
2429         }
2430         switch (option) {
2431           case TN_ECHO:
2432             optionStr = "ECHO";
2433             break;
2434           default:
2435             optionStr = buf2;
2436             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2437             break;
2438         }
2439         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2440     }
2441     msg[0] = TN_IAC;
2442     msg[1] = ddww;
2443     msg[2] = option;
2444     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2445     if (outCount < 3) {
2446         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2447     }
2448 }
2449
2450 void
2451 DoEcho ()
2452 {
2453     if (!appData.icsActive) return;
2454     TelnetRequest(TN_DO, TN_ECHO);
2455 }
2456
2457 void
2458 DontEcho ()
2459 {
2460     if (!appData.icsActive) return;
2461     TelnetRequest(TN_DONT, TN_ECHO);
2462 }
2463
2464 void
2465 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2466 {
2467     /* put the holdings sent to us by the server on the board holdings area */
2468     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2469     char p;
2470     ChessSquare piece;
2471
2472     if(gameInfo.holdingsWidth < 2)  return;
2473     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2474         return; // prevent overwriting by pre-board holdings
2475
2476     if( (int)lowestPiece >= BlackPawn ) {
2477         holdingsColumn = 0;
2478         countsColumn = 1;
2479         holdingsStartRow = BOARD_HEIGHT-1;
2480         direction = -1;
2481     } else {
2482         holdingsColumn = BOARD_WIDTH-1;
2483         countsColumn = BOARD_WIDTH-2;
2484         holdingsStartRow = 0;
2485         direction = 1;
2486     }
2487
2488     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2489         board[i][holdingsColumn] = EmptySquare;
2490         board[i][countsColumn]   = (ChessSquare) 0;
2491     }
2492     while( (p=*holdings++) != NULLCHAR ) {
2493         piece = CharToPiece( ToUpper(p) );
2494         if(piece == EmptySquare) continue;
2495         /*j = (int) piece - (int) WhitePawn;*/
2496         j = PieceToNumber(piece);
2497         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2498         if(j < 0) continue;               /* should not happen */
2499         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2500         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2501         board[holdingsStartRow+j*direction][countsColumn]++;
2502     }
2503 }
2504
2505
2506 void
2507 VariantSwitch (Board board, VariantClass newVariant)
2508 {
2509    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2510    static Board oldBoard;
2511
2512    startedFromPositionFile = FALSE;
2513    if(gameInfo.variant == newVariant) return;
2514
2515    /* [HGM] This routine is called each time an assignment is made to
2516     * gameInfo.variant during a game, to make sure the board sizes
2517     * are set to match the new variant. If that means adding or deleting
2518     * holdings, we shift the playing board accordingly
2519     * This kludge is needed because in ICS observe mode, we get boards
2520     * of an ongoing game without knowing the variant, and learn about the
2521     * latter only later. This can be because of the move list we requested,
2522     * in which case the game history is refilled from the beginning anyway,
2523     * but also when receiving holdings of a crazyhouse game. In the latter
2524     * case we want to add those holdings to the already received position.
2525     */
2526
2527
2528    if (appData.debugMode) {
2529      fprintf(debugFP, "Switch board from %s to %s\n",
2530              VariantName(gameInfo.variant), VariantName(newVariant));
2531      setbuf(debugFP, NULL);
2532    }
2533    shuffleOpenings = 0;       /* [HGM] shuffle */
2534    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2535    switch(newVariant)
2536      {
2537      case VariantShogi:
2538        newWidth = 9;  newHeight = 9;
2539        gameInfo.holdingsSize = 7;
2540      case VariantBughouse:
2541      case VariantCrazyhouse:
2542        newHoldingsWidth = 2; break;
2543      case VariantGreat:
2544        newWidth = 10;
2545      case VariantSuper:
2546        newHoldingsWidth = 2;
2547        gameInfo.holdingsSize = 8;
2548        break;
2549      case VariantGothic:
2550      case VariantCapablanca:
2551      case VariantCapaRandom:
2552        newWidth = 10;
2553      default:
2554        newHoldingsWidth = gameInfo.holdingsSize = 0;
2555      };
2556
2557    if(newWidth  != gameInfo.boardWidth  ||
2558       newHeight != gameInfo.boardHeight ||
2559       newHoldingsWidth != gameInfo.holdingsWidth ) {
2560
2561      /* shift position to new playing area, if needed */
2562      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2563        for(i=0; i<BOARD_HEIGHT; i++)
2564          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2565            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2566              board[i][j];
2567        for(i=0; i<newHeight; i++) {
2568          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2569          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2570        }
2571      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2572        for(i=0; i<BOARD_HEIGHT; i++)
2573          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2574            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2575              board[i][j];
2576      }
2577      board[HOLDINGS_SET] = 0;
2578      gameInfo.boardWidth  = newWidth;
2579      gameInfo.boardHeight = newHeight;
2580      gameInfo.holdingsWidth = newHoldingsWidth;
2581      gameInfo.variant = newVariant;
2582      InitDrawingSizes(-2, 0);
2583    } else gameInfo.variant = newVariant;
2584    CopyBoard(oldBoard, board);   // remember correctly formatted board
2585      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2586    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2587 }
2588
2589 static int loggedOn = FALSE;
2590
2591 /*-- Game start info cache: --*/
2592 int gs_gamenum;
2593 char gs_kind[MSG_SIZ];
2594 static char player1Name[128] = "";
2595 static char player2Name[128] = "";
2596 static char cont_seq[] = "\n\\   ";
2597 static int player1Rating = -1;
2598 static int player2Rating = -1;
2599 /*----------------------------*/
2600
2601 ColorClass curColor = ColorNormal;
2602 int suppressKibitz = 0;
2603
2604 // [HGM] seekgraph
2605 Boolean soughtPending = FALSE;
2606 Boolean seekGraphUp;
2607 #define MAX_SEEK_ADS 200
2608 #define SQUARE 0x80
2609 char *seekAdList[MAX_SEEK_ADS];
2610 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2611 float tcList[MAX_SEEK_ADS];
2612 char colorList[MAX_SEEK_ADS];
2613 int nrOfSeekAds = 0;
2614 int minRating = 1010, maxRating = 2800;
2615 int hMargin = 10, vMargin = 20, h, w;
2616 extern int squareSize, lineGap;
2617
2618 void
2619 PlotSeekAd (int i)
2620 {
2621         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2622         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2623         if(r < minRating+100 && r >=0 ) r = minRating+100;
2624         if(r > maxRating) r = maxRating;
2625         if(tc < 1.f) tc = 1.f;
2626         if(tc > 95.f) tc = 95.f;
2627         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2628         y = ((double)r - minRating)/(maxRating - minRating)
2629             * (h-vMargin-squareSize/8-1) + vMargin;
2630         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2631         if(strstr(seekAdList[i], " u ")) color = 1;
2632         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2633            !strstr(seekAdList[i], "bullet") &&
2634            !strstr(seekAdList[i], "blitz") &&
2635            !strstr(seekAdList[i], "standard") ) color = 2;
2636         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2637         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2638 }
2639
2640 void
2641 PlotSingleSeekAd (int i)
2642 {
2643         PlotSeekAd(i);
2644 }
2645
2646 void
2647 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2648 {
2649         char buf[MSG_SIZ], *ext = "";
2650         VariantClass v = StringToVariant(type);
2651         if(strstr(type, "wild")) {
2652             ext = type + 4; // append wild number
2653             if(v == VariantFischeRandom) type = "chess960"; else
2654             if(v == VariantLoadable) type = "setup"; else
2655             type = VariantName(v);
2656         }
2657         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2658         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2659             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2660             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2661             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2662             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2663             seekNrList[nrOfSeekAds] = nr;
2664             zList[nrOfSeekAds] = 0;
2665             seekAdList[nrOfSeekAds++] = StrSave(buf);
2666             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2667         }
2668 }
2669
2670 void
2671 EraseSeekDot (int i)
2672 {
2673     int x = xList[i], y = yList[i], d=squareSize/4, k;
2674     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2675     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2676     // now replot every dot that overlapped
2677     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2678         int xx = xList[k], yy = yList[k];
2679         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2680             DrawSeekDot(xx, yy, colorList[k]);
2681     }
2682 }
2683
2684 void
2685 RemoveSeekAd (int nr)
2686 {
2687         int i;
2688         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2689             EraseSeekDot(i);
2690             if(seekAdList[i]) free(seekAdList[i]);
2691             seekAdList[i] = seekAdList[--nrOfSeekAds];
2692             seekNrList[i] = seekNrList[nrOfSeekAds];
2693             ratingList[i] = ratingList[nrOfSeekAds];
2694             colorList[i]  = colorList[nrOfSeekAds];
2695             tcList[i] = tcList[nrOfSeekAds];
2696             xList[i]  = xList[nrOfSeekAds];
2697             yList[i]  = yList[nrOfSeekAds];
2698             zList[i]  = zList[nrOfSeekAds];
2699             seekAdList[nrOfSeekAds] = NULL;
2700             break;
2701         }
2702 }
2703
2704 Boolean
2705 MatchSoughtLine (char *line)
2706 {
2707     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2708     int nr, base, inc, u=0; char dummy;
2709
2710     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2711        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2712        (u=1) &&
2713        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2714         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2715         // match: compact and save the line
2716         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2717         return TRUE;
2718     }
2719     return FALSE;
2720 }
2721
2722 int
2723 DrawSeekGraph ()
2724 {
2725     int i;
2726     if(!seekGraphUp) return FALSE;
2727     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2728     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2729
2730     DrawSeekBackground(0, 0, w, h);
2731     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2732     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2733     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2734         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2735         yy = h-1-yy;
2736         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2737         if(i%500 == 0) {
2738             char buf[MSG_SIZ];
2739             snprintf(buf, MSG_SIZ, "%d", i);
2740             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2741         }
2742     }
2743     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2744     for(i=1; i<100; i+=(i<10?1:5)) {
2745         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2746         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2747         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2748             char buf[MSG_SIZ];
2749             snprintf(buf, MSG_SIZ, "%d", i);
2750             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2751         }
2752     }
2753     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2754     return TRUE;
2755 }
2756
2757 int
2758 SeekGraphClick (ClickType click, int x, int y, int moving)
2759 {
2760     static int lastDown = 0, displayed = 0, lastSecond;
2761     if(y < 0) return FALSE;
2762     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2763         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2764         if(!seekGraphUp) return FALSE;
2765         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2766         DrawPosition(TRUE, NULL);
2767         return TRUE;
2768     }
2769     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2770         if(click == Release || moving) return FALSE;
2771         nrOfSeekAds = 0;
2772         soughtPending = TRUE;
2773         SendToICS(ics_prefix);
2774         SendToICS("sought\n"); // should this be "sought all"?
2775     } else { // issue challenge based on clicked ad
2776         int dist = 10000; int i, closest = 0, second = 0;
2777         for(i=0; i<nrOfSeekAds; i++) {
2778             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2779             if(d < dist) { dist = d; closest = i; }
2780             second += (d - zList[i] < 120); // count in-range ads
2781             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2782         }
2783         if(dist < 120) {
2784             char buf[MSG_SIZ];
2785             second = (second > 1);
2786             if(displayed != closest || second != lastSecond) {
2787                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2788                 lastSecond = second; displayed = closest;
2789             }
2790             if(click == Press) {
2791                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2792                 lastDown = closest;
2793                 return TRUE;
2794             } // on press 'hit', only show info
2795             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2796             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2797             SendToICS(ics_prefix);
2798             SendToICS(buf);
2799             return TRUE; // let incoming board of started game pop down the graph
2800         } else if(click == Release) { // release 'miss' is ignored
2801             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2802             if(moving == 2) { // right up-click
2803                 nrOfSeekAds = 0; // refresh graph
2804                 soughtPending = TRUE;
2805                 SendToICS(ics_prefix);
2806                 SendToICS("sought\n"); // should this be "sought all"?
2807             }
2808             return TRUE;
2809         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2810         // press miss or release hit 'pop down' seek graph
2811         seekGraphUp = FALSE;
2812         DrawPosition(TRUE, NULL);
2813     }
2814     return TRUE;
2815 }
2816
2817 void
2818 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2819 {
2820 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2821 #define STARTED_NONE 0
2822 #define STARTED_MOVES 1
2823 #define STARTED_BOARD 2
2824 #define STARTED_OBSERVE 3
2825 #define STARTED_HOLDINGS 4
2826 #define STARTED_CHATTER 5
2827 #define STARTED_COMMENT 6
2828 #define STARTED_MOVES_NOHIDE 7
2829
2830     static int started = STARTED_NONE;
2831     static char parse[20000];
2832     static int parse_pos = 0;
2833     static char buf[BUF_SIZE + 1];
2834     static int firstTime = TRUE, intfSet = FALSE;
2835     static ColorClass prevColor = ColorNormal;
2836     static int savingComment = FALSE;
2837     static int cmatch = 0; // continuation sequence match
2838     char *bp;
2839     char str[MSG_SIZ];
2840     int i, oldi;
2841     int buf_len;
2842     int next_out;
2843     int tkind;
2844     int backup;    /* [DM] For zippy color lines */
2845     char *p;
2846     char talker[MSG_SIZ]; // [HGM] chat
2847     int channel, collective=0;
2848
2849     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2850
2851     if (appData.debugMode) {
2852       if (!error) {
2853         fprintf(debugFP, "<ICS: ");
2854         show_bytes(debugFP, data, count);
2855         fprintf(debugFP, "\n");
2856       }
2857     }
2858
2859     if (appData.debugMode) { int f = forwardMostMove;
2860         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2861                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2862                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2863     }
2864     if (count > 0) {
2865         /* If last read ended with a partial line that we couldn't parse,
2866            prepend it to the new read and try again. */
2867         if (leftover_len > 0) {
2868             for (i=0; i<leftover_len; i++)
2869               buf[i] = buf[leftover_start + i];
2870         }
2871
2872     /* copy new characters into the buffer */
2873     bp = buf + leftover_len;
2874     buf_len=leftover_len;
2875     for (i=0; i<count; i++)
2876     {
2877         // ignore these
2878         if (data[i] == '\r')
2879             continue;
2880
2881         // join lines split by ICS?
2882         if (!appData.noJoin)
2883         {
2884             /*
2885                 Joining just consists of finding matches against the
2886                 continuation sequence, and discarding that sequence
2887                 if found instead of copying it.  So, until a match
2888                 fails, there's nothing to do since it might be the
2889                 complete sequence, and thus, something we don't want
2890                 copied.
2891             */
2892             if (data[i] == cont_seq[cmatch])
2893             {
2894                 cmatch++;
2895                 if (cmatch == strlen(cont_seq))
2896                 {
2897                     cmatch = 0; // complete match.  just reset the counter
2898
2899                     /*
2900                         it's possible for the ICS to not include the space
2901                         at the end of the last word, making our [correct]
2902                         join operation fuse two separate words.  the server
2903                         does this when the space occurs at the width setting.
2904                     */
2905                     if (!buf_len || buf[buf_len-1] != ' ')
2906                     {
2907                         *bp++ = ' ';
2908                         buf_len++;
2909                     }
2910                 }
2911                 continue;
2912             }
2913             else if (cmatch)
2914             {
2915                 /*
2916                     match failed, so we have to copy what matched before
2917                     falling through and copying this character.  In reality,
2918                     this will only ever be just the newline character, but
2919                     it doesn't hurt to be precise.
2920                 */
2921                 strncpy(bp, cont_seq, cmatch);
2922                 bp += cmatch;
2923                 buf_len += cmatch;
2924                 cmatch = 0;
2925             }
2926         }
2927
2928         // copy this char
2929         *bp++ = data[i];
2930         buf_len++;
2931     }
2932
2933         buf[buf_len] = NULLCHAR;
2934 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2935         next_out = 0;
2936         leftover_start = 0;
2937
2938         i = 0;
2939         while (i < buf_len) {
2940             /* Deal with part of the TELNET option negotiation
2941                protocol.  We refuse to do anything beyond the
2942                defaults, except that we allow the WILL ECHO option,
2943                which ICS uses to turn off password echoing when we are
2944                directly connected to it.  We reject this option
2945                if localLineEditing mode is on (always on in xboard)
2946                and we are talking to port 23, which might be a real
2947                telnet server that will try to keep WILL ECHO on permanently.
2948              */
2949             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2950                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2951                 unsigned char option;
2952                 oldi = i;
2953                 switch ((unsigned char) buf[++i]) {
2954                   case TN_WILL:
2955                     if (appData.debugMode)
2956                       fprintf(debugFP, "\n<WILL ");
2957                     switch (option = (unsigned char) buf[++i]) {
2958                       case TN_ECHO:
2959                         if (appData.debugMode)
2960                           fprintf(debugFP, "ECHO ");
2961                         /* Reply only if this is a change, according
2962                            to the protocol rules. */
2963                         if (remoteEchoOption) break;
2964                         if (appData.localLineEditing &&
2965                             atoi(appData.icsPort) == TN_PORT) {
2966                             TelnetRequest(TN_DONT, TN_ECHO);
2967                         } else {
2968                             EchoOff();
2969                             TelnetRequest(TN_DO, TN_ECHO);
2970                             remoteEchoOption = TRUE;
2971                         }
2972                         break;
2973                       default:
2974                         if (appData.debugMode)
2975                           fprintf(debugFP, "%d ", option);
2976                         /* Whatever this is, we don't want it. */
2977                         TelnetRequest(TN_DONT, option);
2978                         break;
2979                     }
2980                     break;
2981                   case TN_WONT:
2982                     if (appData.debugMode)
2983                       fprintf(debugFP, "\n<WONT ");
2984                     switch (option = (unsigned char) buf[++i]) {
2985                       case TN_ECHO:
2986                         if (appData.debugMode)
2987                           fprintf(debugFP, "ECHO ");
2988                         /* Reply only if this is a change, according
2989                            to the protocol rules. */
2990                         if (!remoteEchoOption) break;
2991                         EchoOn();
2992                         TelnetRequest(TN_DONT, TN_ECHO);
2993                         remoteEchoOption = FALSE;
2994                         break;
2995                       default:
2996                         if (appData.debugMode)
2997                           fprintf(debugFP, "%d ", (unsigned char) option);
2998                         /* Whatever this is, it must already be turned
2999                            off, because we never agree to turn on
3000                            anything non-default, so according to the
3001                            protocol rules, we don't reply. */
3002                         break;
3003                     }
3004                     break;
3005                   case TN_DO:
3006                     if (appData.debugMode)
3007                       fprintf(debugFP, "\n<DO ");
3008                     switch (option = (unsigned char) buf[++i]) {
3009                       default:
3010                         /* Whatever this is, we refuse to do it. */
3011                         if (appData.debugMode)
3012                           fprintf(debugFP, "%d ", option);
3013                         TelnetRequest(TN_WONT, option);
3014                         break;
3015                     }
3016                     break;
3017                   case TN_DONT:
3018                     if (appData.debugMode)
3019                       fprintf(debugFP, "\n<DONT ");
3020                     switch (option = (unsigned char) buf[++i]) {
3021                       default:
3022                         if (appData.debugMode)
3023                           fprintf(debugFP, "%d ", option);
3024                         /* Whatever this is, we are already not doing
3025                            it, because we never agree to do anything
3026                            non-default, so according to the protocol
3027                            rules, we don't reply. */
3028                         break;
3029                     }
3030                     break;
3031                   case TN_IAC:
3032                     if (appData.debugMode)
3033                       fprintf(debugFP, "\n<IAC ");
3034                     /* Doubled IAC; pass it through */
3035                     i--;
3036                     break;
3037                   default:
3038                     if (appData.debugMode)
3039                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3040                     /* Drop all other telnet commands on the floor */
3041                     break;
3042                 }
3043                 if (oldi > next_out)
3044                   SendToPlayer(&buf[next_out], oldi - next_out);
3045                 if (++i > next_out)
3046                   next_out = i;
3047                 continue;
3048             }
3049
3050             /* OK, this at least will *usually* work */
3051             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3052                 loggedOn = TRUE;
3053             }
3054
3055             if (loggedOn && !intfSet) {
3056                 if (ics_type == ICS_ICC) {
3057                   snprintf(str, MSG_SIZ,
3058                           "/set-quietly interface %s\n/set-quietly style 12\n",
3059                           programVersion);
3060                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3061                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3062                 } else if (ics_type == ICS_CHESSNET) {
3063                   snprintf(str, MSG_SIZ, "/style 12\n");
3064                 } else {
3065                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3066                   strcat(str, programVersion);
3067                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3068                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3069                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3070 #ifdef WIN32
3071                   strcat(str, "$iset nohighlight 1\n");
3072 #endif
3073                   strcat(str, "$iset lock 1\n$style 12\n");
3074                 }
3075                 SendToICS(str);
3076                 NotifyFrontendLogin();
3077                 intfSet = TRUE;
3078             }
3079
3080             if (started == STARTED_COMMENT) {
3081                 /* Accumulate characters in comment */
3082                 parse[parse_pos++] = buf[i];
3083                 if (buf[i] == '\n') {
3084                     parse[parse_pos] = NULLCHAR;
3085                     if(chattingPartner>=0) {
3086                         char mess[MSG_SIZ];
3087                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3088                         OutputChatMessage(chattingPartner, mess);
3089                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3090                             int p;
3091                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3092                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3093                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3094                                 OutputChatMessage(p, mess);
3095                                 break;
3096                             }
3097                         }
3098                         chattingPartner = -1;
3099                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3100                         collective = 0;
3101                     } else
3102                     if(!suppressKibitz) // [HGM] kibitz
3103                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3104                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3105                         int nrDigit = 0, nrAlph = 0, j;
3106                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3107                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3108                         parse[parse_pos] = NULLCHAR;
3109                         // try to be smart: if it does not look like search info, it should go to
3110                         // ICS interaction window after all, not to engine-output window.
3111                         for(j=0; j<parse_pos; j++) { // count letters and digits
3112                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3113                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3114                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3115                         }
3116                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3117                             int depth=0; float score;
3118                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3119                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3120                                 pvInfoList[forwardMostMove-1].depth = depth;
3121                                 pvInfoList[forwardMostMove-1].score = 100*score;
3122                             }
3123                             OutputKibitz(suppressKibitz, parse);
3124                         } else {
3125                             char tmp[MSG_SIZ];
3126                             if(gameMode == IcsObserving) // restore original ICS messages
3127                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3128                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3129                             else
3130                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3131                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3132                             SendToPlayer(tmp, strlen(tmp));
3133                         }
3134                         next_out = i+1; // [HGM] suppress printing in ICS window
3135                     }
3136                     started = STARTED_NONE;
3137                 } else {
3138                     /* Don't match patterns against characters in comment */
3139                     i++;
3140                     continue;
3141                 }
3142             }
3143             if (started == STARTED_CHATTER) {
3144                 if (buf[i] != '\n') {
3145                     /* Don't match patterns against characters in chatter */
3146                     i++;
3147                     continue;
3148                 }
3149                 started = STARTED_NONE;
3150                 if(suppressKibitz) next_out = i+1;
3151             }
3152
3153             /* Kludge to deal with rcmd protocol */
3154             if (firstTime && looking_at(buf, &i, "\001*")) {
3155                 DisplayFatalError(&buf[1], 0, 1);
3156                 continue;
3157             } else {
3158                 firstTime = FALSE;
3159             }
3160
3161             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3162                 ics_type = ICS_ICC;
3163                 ics_prefix = "/";
3164                 if (appData.debugMode)
3165                   fprintf(debugFP, "ics_type %d\n", ics_type);
3166                 continue;
3167             }
3168             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3169                 ics_type = ICS_FICS;
3170                 ics_prefix = "$";
3171                 if (appData.debugMode)
3172                   fprintf(debugFP, "ics_type %d\n", ics_type);
3173                 continue;
3174             }
3175             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3176                 ics_type = ICS_CHESSNET;
3177                 ics_prefix = "/";
3178                 if (appData.debugMode)
3179                   fprintf(debugFP, "ics_type %d\n", ics_type);
3180                 continue;
3181             }
3182
3183             if (!loggedOn &&
3184                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3185                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3186                  looking_at(buf, &i, "will be \"*\""))) {
3187               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3188               continue;
3189             }
3190
3191             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3192               char buf[MSG_SIZ];
3193               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3194               DisplayIcsInteractionTitle(buf);
3195               have_set_title = TRUE;
3196             }
3197
3198             /* skip finger notes */
3199             if (started == STARTED_NONE &&
3200                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3201                  (buf[i] == '1' && buf[i+1] == '0')) &&
3202                 buf[i+2] == ':' && buf[i+3] == ' ') {
3203               started = STARTED_CHATTER;
3204               i += 3;
3205               continue;
3206             }
3207
3208             oldi = i;
3209             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3210             if(appData.seekGraph) {
3211                 if(soughtPending && MatchSoughtLine(buf+i)) {
3212                     i = strstr(buf+i, "rated") - buf;
3213                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3214                     next_out = leftover_start = i;
3215                     started = STARTED_CHATTER;
3216                     suppressKibitz = TRUE;
3217                     continue;
3218                 }
3219                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3220                         && looking_at(buf, &i, "* ads displayed")) {
3221                     soughtPending = FALSE;
3222                     seekGraphUp = TRUE;
3223                     DrawSeekGraph();
3224                     continue;
3225                 }
3226                 if(appData.autoRefresh) {
3227                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3228                         int s = (ics_type == ICS_ICC); // ICC format differs
3229                         if(seekGraphUp)
3230                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3231                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3232                         looking_at(buf, &i, "*% "); // eat prompt
3233                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3234                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3235                         next_out = i; // suppress
3236                         continue;
3237                     }
3238                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3239                         char *p = star_match[0];
3240                         while(*p) {
3241                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3242                             while(*p && *p++ != ' '); // next
3243                         }
3244                         looking_at(buf, &i, "*% "); // eat prompt
3245                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3246                         next_out = i;
3247                         continue;
3248                     }
3249                 }
3250             }
3251
3252             /* skip formula vars */
3253             if (started == STARTED_NONE &&
3254                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3255               started = STARTED_CHATTER;
3256               i += 3;
3257               continue;
3258             }
3259
3260             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3261             if (appData.autoKibitz && started == STARTED_NONE &&
3262                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3263                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3264                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3265                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3266                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3267                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3268                         suppressKibitz = TRUE;
3269                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3270                         next_out = i;
3271                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3272                                 && (gameMode == IcsPlayingWhite)) ||
3273                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3274                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3275                             started = STARTED_CHATTER; // own kibitz we simply discard
3276                         else {
3277                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3278                             parse_pos = 0; parse[0] = NULLCHAR;
3279                             savingComment = TRUE;
3280                             suppressKibitz = gameMode != IcsObserving ? 2 :
3281                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3282                         }
3283                         continue;
3284                 } else
3285                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3286                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3287                          && atoi(star_match[0])) {
3288                     // suppress the acknowledgements of our own autoKibitz
3289                     char *p;
3290                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3291                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3292                     SendToPlayer(star_match[0], strlen(star_match[0]));
3293                     if(looking_at(buf, &i, "*% ")) // eat prompt
3294                         suppressKibitz = FALSE;
3295                     next_out = i;
3296                     continue;
3297                 }
3298             } // [HGM] kibitz: end of patch
3299
3300             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3301
3302             // [HGM] chat: intercept tells by users for which we have an open chat window
3303             channel = -1;
3304             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3305                                            looking_at(buf, &i, "* whispers:") ||
3306                                            looking_at(buf, &i, "* kibitzes:") ||
3307                                            looking_at(buf, &i, "* shouts:") ||
3308                                            looking_at(buf, &i, "* c-shouts:") ||
3309                                            looking_at(buf, &i, "--> * ") ||
3310                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3311                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3312                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3313                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3314                 int p;
3315                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3316                 chattingPartner = -1; collective = 0;
3317
3318                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3319                 for(p=0; p<MAX_CHAT; p++) {
3320                     collective = 1;
3321                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3322                     talker[0] = '['; strcat(talker, "] ");
3323                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3324                     chattingPartner = p; break;
3325                     }
3326                 } else
3327                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3328                 for(p=0; p<MAX_CHAT; p++) {
3329                     collective = 1;
3330                     if(!strcmp("kibitzes", chatPartner[p])) {
3331                         talker[0] = '['; strcat(talker, "] ");
3332                         chattingPartner = p; break;
3333                     }
3334                 } else
3335                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3336                 for(p=0; p<MAX_CHAT; p++) {
3337                     collective = 1;
3338                     if(!strcmp("whispers", chatPartner[p])) {
3339                         talker[0] = '['; strcat(talker, "] ");
3340                         chattingPartner = p; break;
3341                     }
3342                 } else
3343                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3344                   if(buf[i-8] == '-' && buf[i-3] == 't')
3345                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3346                     collective = 1;
3347                     if(!strcmp("c-shouts", chatPartner[p])) {
3348                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3349                         chattingPartner = p; break;
3350                     }
3351                   }
3352                   if(chattingPartner < 0)
3353                   for(p=0; p<MAX_CHAT; p++) {
3354                     collective = 1;
3355                     if(!strcmp("shouts", chatPartner[p])) {
3356                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3357                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3358                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3359                         chattingPartner = p; break;
3360                     }
3361                   }
3362                 }
3363                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3364                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3365                     talker[0] = 0;
3366                     Colorize(ColorTell, FALSE);
3367                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3368                     collective |= 2;
3369                     chattingPartner = p; break;
3370                 }
3371                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3372                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3373                     started = STARTED_COMMENT;
3374                     parse_pos = 0; parse[0] = NULLCHAR;
3375                     savingComment = 3 + chattingPartner; // counts as TRUE
3376                     if(collective == 3) i = oldi; else {
3377                         suppressKibitz = TRUE;
3378                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3379                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3380                         continue;
3381                     }
3382                 }
3383             } // [HGM] chat: end of patch
3384
3385           backup = i;
3386             if (appData.zippyTalk || appData.zippyPlay) {
3387                 /* [DM] Backup address for color zippy lines */
3388 #if ZIPPY
3389                if (loggedOn == TRUE)
3390                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3391                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3392 #endif
3393             } // [DM] 'else { ' deleted
3394                 if (
3395                     /* Regular tells and says */
3396                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3397                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3398                     looking_at(buf, &i, "* says: ") ||
3399                     /* Don't color "message" or "messages" output */
3400                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3401                     looking_at(buf, &i, "*. * at *:*: ") ||
3402                     looking_at(buf, &i, "--* (*:*): ") ||
3403                     /* Message notifications (same color as tells) */
3404                     looking_at(buf, &i, "* has left a message ") ||
3405                     looking_at(buf, &i, "* just sent you a message:\n") ||
3406                     /* Whispers and kibitzes */
3407                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3408                     looking_at(buf, &i, "* kibitzes: ") ||
3409                     /* Channel tells */
3410                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3411
3412                   if (tkind == 1 && strchr(star_match[0], ':')) {
3413                       /* Avoid "tells you:" spoofs in channels */
3414                      tkind = 3;
3415                   }
3416                   if (star_match[0][0] == NULLCHAR ||
3417                       strchr(star_match[0], ' ') ||
3418                       (tkind == 3 && strchr(star_match[1], ' '))) {
3419                     /* Reject bogus matches */
3420                     i = oldi;
3421                   } else {
3422                     if (appData.colorize) {
3423                       if (oldi > next_out) {
3424                         SendToPlayer(&buf[next_out], oldi - next_out);
3425                         next_out = oldi;
3426                       }
3427                       switch (tkind) {
3428                       case 1:
3429                         Colorize(ColorTell, FALSE);
3430                         curColor = ColorTell;
3431                         break;
3432                       case 2:
3433                         Colorize(ColorKibitz, FALSE);
3434                         curColor = ColorKibitz;
3435                         break;
3436                       case 3:
3437                         p = strrchr(star_match[1], '(');
3438                         if (p == NULL) {
3439                           p = star_match[1];
3440                         } else {
3441                           p++;
3442                         }
3443                         if (atoi(p) == 1) {
3444                           Colorize(ColorChannel1, FALSE);
3445                           curColor = ColorChannel1;
3446                         } else {
3447                           Colorize(ColorChannel, FALSE);
3448                           curColor = ColorChannel;
3449                         }
3450                         break;
3451                       case 5:
3452                         curColor = ColorNormal;
3453                         break;
3454                       }
3455                     }
3456                     if (started == STARTED_NONE && appData.autoComment &&
3457                         (gameMode == IcsObserving ||
3458                          gameMode == IcsPlayingWhite ||
3459                          gameMode == IcsPlayingBlack)) {
3460                       parse_pos = i - oldi;
3461                       memcpy(parse, &buf[oldi], parse_pos);
3462                       parse[parse_pos] = NULLCHAR;
3463                       started = STARTED_COMMENT;
3464                       savingComment = TRUE;
3465                     } else if(collective != 3) {
3466                       started = STARTED_CHATTER;
3467                       savingComment = FALSE;
3468                     }
3469                     loggedOn = TRUE;
3470                     continue;
3471                   }
3472                 }
3473
3474                 if (looking_at(buf, &i, "* s-shouts: ") ||
3475                     looking_at(buf, &i, "* c-shouts: ")) {
3476                     if (appData.colorize) {
3477                         if (oldi > next_out) {
3478                             SendToPlayer(&buf[next_out], oldi - next_out);
3479                             next_out = oldi;
3480                         }
3481                         Colorize(ColorSShout, FALSE);
3482                         curColor = ColorSShout;
3483                     }
3484                     loggedOn = TRUE;
3485                     started = STARTED_CHATTER;
3486                     continue;
3487                 }
3488
3489                 if (looking_at(buf, &i, "--->")) {
3490                     loggedOn = TRUE;
3491                     continue;
3492                 }
3493
3494                 if (looking_at(buf, &i, "* shouts: ") ||
3495                     looking_at(buf, &i, "--> ")) {
3496                     if (appData.colorize) {
3497                         if (oldi > next_out) {
3498                             SendToPlayer(&buf[next_out], oldi - next_out);
3499                             next_out = oldi;
3500                         }
3501                         Colorize(ColorShout, FALSE);
3502                         curColor = ColorShout;
3503                     }
3504                     loggedOn = TRUE;
3505                     started = STARTED_CHATTER;
3506                     continue;
3507                 }
3508
3509                 if (looking_at( buf, &i, "Challenge:")) {
3510                     if (appData.colorize) {
3511                         if (oldi > next_out) {
3512                             SendToPlayer(&buf[next_out], oldi - next_out);
3513                             next_out = oldi;
3514                         }
3515                         Colorize(ColorChallenge, FALSE);
3516                         curColor = ColorChallenge;
3517                     }
3518                     loggedOn = TRUE;
3519                     continue;
3520                 }
3521
3522                 if (looking_at(buf, &i, "* offers you") ||
3523                     looking_at(buf, &i, "* offers to be") ||
3524                     looking_at(buf, &i, "* would like to") ||
3525                     looking_at(buf, &i, "* requests to") ||
3526                     looking_at(buf, &i, "Your opponent offers") ||
3527                     looking_at(buf, &i, "Your opponent requests")) {
3528
3529                     if (appData.colorize) {
3530                         if (oldi > next_out) {
3531                             SendToPlayer(&buf[next_out], oldi - next_out);
3532                             next_out = oldi;
3533                         }
3534                         Colorize(ColorRequest, FALSE);
3535                         curColor = ColorRequest;
3536                     }
3537                     continue;
3538                 }
3539
3540                 if (looking_at(buf, &i, "* (*) seeking")) {
3541                     if (appData.colorize) {
3542                         if (oldi > next_out) {
3543                             SendToPlayer(&buf[next_out], oldi - next_out);
3544                             next_out = oldi;
3545                         }
3546                         Colorize(ColorSeek, FALSE);
3547                         curColor = ColorSeek;
3548                     }
3549                     continue;
3550             }
3551
3552           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3553
3554             if (looking_at(buf, &i, "\\   ")) {
3555                 if (prevColor != ColorNormal) {
3556                     if (oldi > next_out) {
3557                         SendToPlayer(&buf[next_out], oldi - next_out);
3558                         next_out = oldi;
3559                     }
3560                     Colorize(prevColor, TRUE);
3561                     curColor = prevColor;
3562                 }
3563                 if (savingComment) {
3564                     parse_pos = i - oldi;
3565                     memcpy(parse, &buf[oldi], parse_pos);
3566                     parse[parse_pos] = NULLCHAR;
3567                     started = STARTED_COMMENT;
3568                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3569                         chattingPartner = savingComment - 3; // kludge to remember the box
3570                 } else {
3571                     started = STARTED_CHATTER;
3572                 }
3573                 continue;
3574             }
3575
3576             if (looking_at(buf, &i, "Black Strength :") ||
3577                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3578                 looking_at(buf, &i, "<10>") ||
3579                 looking_at(buf, &i, "#@#")) {
3580                 /* Wrong board style */
3581                 loggedOn = TRUE;
3582                 SendToICS(ics_prefix);
3583                 SendToICS("set style 12\n");
3584                 SendToICS(ics_prefix);
3585                 SendToICS("refresh\n");
3586                 continue;
3587             }
3588
3589             if (looking_at(buf, &i, "login:")) {
3590               if (!have_sent_ICS_logon) {
3591                 if(ICSInitScript())
3592                   have_sent_ICS_logon = 1;
3593                 else // no init script was found
3594                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3595               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3596                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3597               }
3598                 continue;
3599             }
3600
3601             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3602                 (looking_at(buf, &i, "\n<12> ") ||
3603                  looking_at(buf, &i, "<12> "))) {
3604                 loggedOn = TRUE;
3605                 if (oldi > next_out) {
3606                     SendToPlayer(&buf[next_out], oldi - next_out);
3607                 }
3608                 next_out = i;
3609                 started = STARTED_BOARD;
3610                 parse_pos = 0;
3611                 continue;
3612             }
3613
3614             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3615                 looking_at(buf, &i, "<b1> ")) {
3616                 if (oldi > next_out) {
3617                     SendToPlayer(&buf[next_out], oldi - next_out);
3618                 }
3619                 next_out = i;
3620                 started = STARTED_HOLDINGS;
3621                 parse_pos = 0;
3622                 continue;
3623             }
3624
3625             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3626                 loggedOn = TRUE;
3627                 /* Header for a move list -- first line */
3628
3629                 switch (ics_getting_history) {
3630                   case H_FALSE:
3631                     switch (gameMode) {
3632                       case IcsIdle:
3633                       case BeginningOfGame:
3634                         /* User typed "moves" or "oldmoves" while we
3635                            were idle.  Pretend we asked for these
3636                            moves and soak them up so user can step
3637                            through them and/or save them.
3638                            */
3639                         Reset(FALSE, TRUE);
3640                         gameMode = IcsObserving;
3641                         ModeHighlight();
3642                         ics_gamenum = -1;
3643                         ics_getting_history = H_GOT_UNREQ_HEADER;
3644                         break;
3645                       case EditGame: /*?*/
3646                       case EditPosition: /*?*/
3647                         /* Should above feature work in these modes too? */
3648                         /* For now it doesn't */
3649                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3650                         break;
3651                       default:
3652                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3653                         break;
3654                     }
3655                     break;
3656                   case H_REQUESTED:
3657                     /* Is this the right one? */
3658                     if (gameInfo.white && gameInfo.black &&
3659                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3660                         strcmp(gameInfo.black, star_match[2]) == 0) {
3661                         /* All is well */
3662                         ics_getting_history = H_GOT_REQ_HEADER;
3663                     }
3664                     break;
3665                   case H_GOT_REQ_HEADER:
3666                   case H_GOT_UNREQ_HEADER:
3667                   case H_GOT_UNWANTED_HEADER:
3668                   case H_GETTING_MOVES:
3669                     /* Should not happen */
3670                     DisplayError(_("Error gathering move list: two headers"), 0);
3671                     ics_getting_history = H_FALSE;
3672                     break;
3673                 }
3674
3675                 /* Save player ratings into gameInfo if needed */
3676                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3677                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3678                     (gameInfo.whiteRating == -1 ||
3679                      gameInfo.blackRating == -1)) {
3680
3681                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3682                     gameInfo.blackRating = string_to_rating(star_match[3]);
3683                     if (appData.debugMode)
3684                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3685                               gameInfo.whiteRating, gameInfo.blackRating);
3686                 }
3687                 continue;
3688             }
3689
3690             if (looking_at(buf, &i,
3691               "* * match, initial time: * minute*, increment: * second")) {
3692                 /* Header for a move list -- second line */
3693                 /* Initial board will follow if this is a wild game */
3694                 if (gameInfo.event != NULL) free(gameInfo.event);
3695                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3696                 gameInfo.event = StrSave(str);
3697                 /* [HGM] we switched variant. Translate boards if needed. */
3698                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3699                 continue;
3700             }
3701
3702             if (looking_at(buf, &i, "Move  ")) {
3703                 /* Beginning of a move list */
3704                 switch (ics_getting_history) {
3705                   case H_FALSE:
3706                     /* Normally should not happen */
3707                     /* Maybe user hit reset while we were parsing */
3708                     break;
3709                   case H_REQUESTED:
3710                     /* Happens if we are ignoring a move list that is not
3711                      * the one we just requested.  Common if the user
3712                      * tries to observe two games without turning off
3713                      * getMoveList */
3714                     break;
3715                   case H_GETTING_MOVES:
3716                     /* Should not happen */
3717                     DisplayError(_("Error gathering move list: nested"), 0);
3718                     ics_getting_history = H_FALSE;
3719                     break;
3720                   case H_GOT_REQ_HEADER:
3721                     ics_getting_history = H_GETTING_MOVES;
3722                     started = STARTED_MOVES;
3723                     parse_pos = 0;
3724                     if (oldi > next_out) {
3725                         SendToPlayer(&buf[next_out], oldi - next_out);
3726                     }
3727                     break;
3728                   case H_GOT_UNREQ_HEADER:
3729                     ics_getting_history = H_GETTING_MOVES;
3730                     started = STARTED_MOVES_NOHIDE;
3731                     parse_pos = 0;
3732                     break;
3733                   case H_GOT_UNWANTED_HEADER:
3734                     ics_getting_history = H_FALSE;
3735                     break;
3736                 }
3737                 continue;
3738             }
3739
3740             if (looking_at(buf, &i, "% ") ||
3741                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3742                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3743                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3744                     soughtPending = FALSE;
3745                     seekGraphUp = TRUE;
3746                     DrawSeekGraph();
3747                 }
3748                 if(suppressKibitz) next_out = i;
3749                 savingComment = FALSE;
3750                 suppressKibitz = 0;
3751                 switch (started) {
3752                   case STARTED_MOVES:
3753                   case STARTED_MOVES_NOHIDE:
3754                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3755                     parse[parse_pos + i - oldi] = NULLCHAR;
3756                     ParseGameHistory(parse);
3757 #if ZIPPY
3758                     if (appData.zippyPlay && first.initDone) {
3759                         FeedMovesToProgram(&first, forwardMostMove);
3760                         if (gameMode == IcsPlayingWhite) {
3761                             if (WhiteOnMove(forwardMostMove)) {
3762                                 if (first.sendTime) {
3763                                   if (first.useColors) {
3764                                     SendToProgram("black\n", &first);
3765                                   }
3766                                   SendTimeRemaining(&first, TRUE);
3767                                 }
3768                                 if (first.useColors) {
3769                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3770                                 }
3771                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3772                                 first.maybeThinking = TRUE;
3773                             } else {
3774                                 if (first.usePlayother) {
3775                                   if (first.sendTime) {
3776                                     SendTimeRemaining(&first, TRUE);
3777                                   }
3778                                   SendToProgram("playother\n", &first);
3779                                   firstMove = FALSE;
3780                                 } else {
3781                                   firstMove = TRUE;
3782                                 }
3783                             }
3784                         } else if (gameMode == IcsPlayingBlack) {
3785                             if (!WhiteOnMove(forwardMostMove)) {
3786                                 if (first.sendTime) {
3787                                   if (first.useColors) {
3788                                     SendToProgram("white\n", &first);
3789                                   }
3790                                   SendTimeRemaining(&first, FALSE);
3791                                 }
3792                                 if (first.useColors) {
3793                                   SendToProgram("black\n", &first);
3794                                 }
3795                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3796                                 first.maybeThinking = TRUE;
3797                             } else {
3798                                 if (first.usePlayother) {
3799                                   if (first.sendTime) {
3800                                     SendTimeRemaining(&first, FALSE);
3801                                   }
3802                                   SendToProgram("playother\n", &first);
3803                                   firstMove = FALSE;
3804                                 } else {
3805                                   firstMove = TRUE;
3806                                 }
3807                             }
3808                         }
3809                     }
3810 #endif
3811                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3812                         /* Moves came from oldmoves or moves command
3813                            while we weren't doing anything else.
3814                            */
3815                         currentMove = forwardMostMove;
3816                         ClearHighlights();/*!!could figure this out*/
3817                         flipView = appData.flipView;
3818                         DrawPosition(TRUE, boards[currentMove]);
3819                         DisplayBothClocks();
3820                         snprintf(str, MSG_SIZ, "%s %s %s",
3821                                 gameInfo.white, _("vs."),  gameInfo.black);
3822                         DisplayTitle(str);
3823                         gameMode = IcsIdle;
3824                     } else {
3825                         /* Moves were history of an active game */
3826                         if (gameInfo.resultDetails != NULL) {
3827                             free(gameInfo.resultDetails);
3828                             gameInfo.resultDetails = NULL;
3829                         }
3830                     }
3831                     HistorySet(parseList, backwardMostMove,
3832                                forwardMostMove, currentMove-1);
3833                     DisplayMove(currentMove - 1);
3834                     if (started == STARTED_MOVES) next_out = i;
3835                     started = STARTED_NONE;
3836                     ics_getting_history = H_FALSE;
3837                     break;
3838
3839                   case STARTED_OBSERVE:
3840                     started = STARTED_NONE;
3841                     SendToICS(ics_prefix);
3842                     SendToICS("refresh\n");
3843                     break;
3844
3845                   default:
3846                     break;
3847                 }
3848                 if(bookHit) { // [HGM] book: simulate book reply
3849                     static char bookMove[MSG_SIZ]; // a bit generous?
3850
3851                     programStats.nodes = programStats.depth = programStats.time =
3852                     programStats.score = programStats.got_only_move = 0;
3853                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3854
3855                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3856                     strcat(bookMove, bookHit);
3857                     HandleMachineMove(bookMove, &first);
3858                 }
3859                 continue;
3860             }
3861
3862             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3863                  started == STARTED_HOLDINGS ||
3864                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3865                 /* Accumulate characters in move list or board */
3866                 parse[parse_pos++] = buf[i];
3867             }
3868
3869             /* Start of game messages.  Mostly we detect start of game
3870                when the first board image arrives.  On some versions
3871                of the ICS, though, we need to do a "refresh" after starting
3872                to observe in order to get the current board right away. */
3873             if (looking_at(buf, &i, "Adding game * to observation list")) {
3874                 started = STARTED_OBSERVE;
3875                 continue;
3876             }
3877
3878             /* Handle auto-observe */
3879             if (appData.autoObserve &&
3880                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3881                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3882                 char *player;
3883                 /* Choose the player that was highlighted, if any. */
3884                 if (star_match[0][0] == '\033' ||
3885                     star_match[1][0] != '\033') {
3886                     player = star_match[0];
3887                 } else {
3888                     player = star_match[2];
3889                 }
3890                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3891                         ics_prefix, StripHighlightAndTitle(player));
3892                 SendToICS(str);
3893
3894                 /* Save ratings from notify string */
3895                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3896                 player1Rating = string_to_rating(star_match[1]);
3897                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3898                 player2Rating = string_to_rating(star_match[3]);
3899
3900                 if (appData.debugMode)
3901                   fprintf(debugFP,
3902                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3903                           player1Name, player1Rating,
3904                           player2Name, player2Rating);
3905
3906                 continue;
3907             }
3908
3909             /* Deal with automatic examine mode after a game,
3910                and with IcsObserving -> IcsExamining transition */
3911             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3912                 looking_at(buf, &i, "has made you an examiner of game *")) {
3913
3914                 int gamenum = atoi(star_match[0]);
3915                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3916                     gamenum == ics_gamenum) {
3917                     /* We were already playing or observing this game;
3918                        no need to refetch history */
3919                     gameMode = IcsExamining;
3920                     if (pausing) {
3921                         pauseExamForwardMostMove = forwardMostMove;
3922                     } else if (currentMove < forwardMostMove) {
3923                         ForwardInner(forwardMostMove);
3924                     }
3925                 } else {
3926                     /* I don't think this case really can happen */
3927                     SendToICS(ics_prefix);
3928                     SendToICS("refresh\n");
3929                 }
3930                 continue;
3931             }
3932
3933             /* Error messages */
3934 //          if (ics_user_moved) {
3935             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3936                 if (looking_at(buf, &i, "Illegal move") ||
3937                     looking_at(buf, &i, "Not a legal move") ||
3938                     looking_at(buf, &i, "Your king is in check") ||
3939                     looking_at(buf, &i, "It isn't your turn") ||
3940                     looking_at(buf, &i, "It is not your move")) {
3941                     /* Illegal move */
3942                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3943                         currentMove = forwardMostMove-1;
3944                         DisplayMove(currentMove - 1); /* before DMError */
3945                         DrawPosition(FALSE, boards[currentMove]);
3946                         SwitchClocks(forwardMostMove-1); // [HGM] race
3947                         DisplayBothClocks();
3948                     }
3949                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3950                     ics_user_moved = 0;
3951                     continue;
3952                 }
3953             }
3954
3955             if (looking_at(buf, &i, "still have time") ||
3956                 looking_at(buf, &i, "not out of time") ||
3957                 looking_at(buf, &i, "either player is out of time") ||
3958                 looking_at(buf, &i, "has timeseal; checking")) {
3959                 /* We must have called his flag a little too soon */
3960                 whiteFlag = blackFlag = FALSE;
3961                 continue;
3962             }
3963
3964             if (looking_at(buf, &i, "added * seconds to") ||
3965                 looking_at(buf, &i, "seconds were added to")) {
3966                 /* Update the clocks */
3967                 SendToICS(ics_prefix);
3968                 SendToICS("refresh\n");
3969                 continue;
3970             }
3971
3972             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3973                 ics_clock_paused = TRUE;
3974                 StopClocks();
3975                 continue;
3976             }
3977
3978             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3979                 ics_clock_paused = FALSE;
3980                 StartClocks();
3981                 continue;
3982             }
3983
3984             /* Grab player ratings from the Creating: message.
3985                Note we have to check for the special case when
3986                the ICS inserts things like [white] or [black]. */
3987             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3988                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3989                 /* star_matches:
3990                    0    player 1 name (not necessarily white)
3991                    1    player 1 rating
3992                    2    empty, white, or black (IGNORED)
3993                    3    player 2 name (not necessarily black)
3994                    4    player 2 rating
3995
3996                    The names/ratings are sorted out when the game
3997                    actually starts (below).
3998                 */
3999                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4000                 player1Rating = string_to_rating(star_match[1]);
4001                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4002                 player2Rating = string_to_rating(star_match[4]);
4003
4004                 if (appData.debugMode)
4005                   fprintf(debugFP,
4006                           "Ratings from 'Creating:' %s %d, %s %d\n",
4007                           player1Name, player1Rating,
4008                           player2Name, player2Rating);
4009
4010                 continue;
4011             }
4012
4013             /* Improved generic start/end-of-game messages */
4014             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4015                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4016                 /* If tkind == 0: */
4017                 /* star_match[0] is the game number */
4018                 /*           [1] is the white player's name */
4019                 /*           [2] is the black player's name */
4020                 /* For end-of-game: */
4021                 /*           [3] is the reason for the game end */
4022                 /*           [4] is a PGN end game-token, preceded by " " */
4023                 /* For start-of-game: */
4024                 /*           [3] begins with "Creating" or "Continuing" */
4025                 /*           [4] is " *" or empty (don't care). */
4026                 int gamenum = atoi(star_match[0]);
4027                 char *whitename, *blackname, *why, *endtoken;
4028                 ChessMove endtype = EndOfFile;
4029
4030                 if (tkind == 0) {
4031                   whitename = star_match[1];
4032                   blackname = star_match[2];
4033                   why = star_match[3];
4034                   endtoken = star_match[4];
4035                 } else {
4036                   whitename = star_match[1];
4037                   blackname = star_match[3];
4038                   why = star_match[5];
4039                   endtoken = star_match[6];
4040                 }
4041
4042                 /* Game start messages */
4043                 if (strncmp(why, "Creating ", 9) == 0 ||
4044                     strncmp(why, "Continuing ", 11) == 0) {
4045                     gs_gamenum = gamenum;
4046                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4047                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4048                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4049 #if ZIPPY
4050                     if (appData.zippyPlay) {
4051                         ZippyGameStart(whitename, blackname);
4052                     }
4053 #endif /*ZIPPY*/
4054                     partnerBoardValid = FALSE; // [HGM] bughouse
4055                     continue;
4056                 }
4057
4058                 /* Game end messages */
4059                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4060                     ics_gamenum != gamenum) {
4061                     continue;
4062                 }
4063                 while (endtoken[0] == ' ') endtoken++;
4064                 switch (endtoken[0]) {
4065                   case '*':
4066                   default:
4067                     endtype = GameUnfinished;
4068                     break;
4069                   case '0':
4070                     endtype = BlackWins;
4071                     break;
4072                   case '1':
4073                     if (endtoken[1] == '/')
4074                       endtype = GameIsDrawn;
4075                     else
4076                       endtype = WhiteWins;
4077                     break;
4078                 }
4079                 GameEnds(endtype, why, GE_ICS);
4080 #if ZIPPY
4081                 if (appData.zippyPlay && first.initDone) {
4082                     ZippyGameEnd(endtype, why);
4083                     if (first.pr == NoProc) {
4084                       /* Start the next process early so that we'll
4085                          be ready for the next challenge */
4086                       StartChessProgram(&first);
4087                     }
4088                     /* Send "new" early, in case this command takes
4089                        a long time to finish, so that we'll be ready
4090                        for the next challenge. */
4091                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4092                     Reset(TRUE, TRUE);
4093                 }
4094 #endif /*ZIPPY*/
4095                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4096                 continue;
4097             }
4098
4099             if (looking_at(buf, &i, "Removing game * from observation") ||
4100                 looking_at(buf, &i, "no longer observing game *") ||
4101                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4102                 if (gameMode == IcsObserving &&
4103                     atoi(star_match[0]) == ics_gamenum)
4104                   {
4105                       /* icsEngineAnalyze */
4106                       if (appData.icsEngineAnalyze) {
4107                             ExitAnalyzeMode();
4108                             ModeHighlight();
4109                       }
4110                       StopClocks();
4111                       gameMode = IcsIdle;
4112                       ics_gamenum = -1;
4113                       ics_user_moved = FALSE;
4114                   }
4115                 continue;
4116             }
4117
4118             if (looking_at(buf, &i, "no longer examining game *")) {
4119                 if (gameMode == IcsExamining &&
4120                     atoi(star_match[0]) == ics_gamenum)
4121                   {
4122                       gameMode = IcsIdle;
4123                       ics_gamenum = -1;
4124                       ics_user_moved = FALSE;
4125                   }
4126                 continue;
4127             }
4128
4129             /* Advance leftover_start past any newlines we find,
4130                so only partial lines can get reparsed */
4131             if (looking_at(buf, &i, "\n")) {
4132                 prevColor = curColor;
4133                 if (curColor != ColorNormal) {
4134                     if (oldi > next_out) {
4135                         SendToPlayer(&buf[next_out], oldi - next_out);
4136                         next_out = oldi;
4137                     }
4138                     Colorize(ColorNormal, FALSE);
4139                     curColor = ColorNormal;
4140                 }
4141                 if (started == STARTED_BOARD) {
4142                     started = STARTED_NONE;
4143                     parse[parse_pos] = NULLCHAR;
4144                     ParseBoard12(parse);
4145                     ics_user_moved = 0;
4146
4147                     /* Send premove here */
4148                     if (appData.premove) {
4149                       char str[MSG_SIZ];
4150                       if (currentMove == 0 &&
4151                           gameMode == IcsPlayingWhite &&
4152                           appData.premoveWhite) {
4153                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4154                         if (appData.debugMode)
4155                           fprintf(debugFP, "Sending premove:\n");
4156                         SendToICS(str);
4157                       } else if (currentMove == 1 &&
4158                                  gameMode == IcsPlayingBlack &&
4159                                  appData.premoveBlack) {
4160                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4161                         if (appData.debugMode)
4162                           fprintf(debugFP, "Sending premove:\n");
4163                         SendToICS(str);
4164                       } else if (gotPremove) {
4165                         gotPremove = 0;
4166                         ClearPremoveHighlights();
4167                         if (appData.debugMode)
4168                           fprintf(debugFP, "Sending premove:\n");
4169                           UserMoveEvent(premoveFromX, premoveFromY,
4170                                         premoveToX, premoveToY,
4171                                         premovePromoChar);
4172                       }
4173                     }
4174
4175                     /* Usually suppress following prompt */
4176                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4177                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4178                         if (looking_at(buf, &i, "*% ")) {
4179                             savingComment = FALSE;
4180                             suppressKibitz = 0;
4181                         }
4182                     }
4183                     next_out = i;
4184                 } else if (started == STARTED_HOLDINGS) {
4185                     int gamenum;
4186                     char new_piece[MSG_SIZ];
4187                     started = STARTED_NONE;
4188                     parse[parse_pos] = NULLCHAR;
4189                     if (appData.debugMode)
4190                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4191                                                         parse, currentMove);
4192                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4193                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4194                         if (gameInfo.variant == VariantNormal) {
4195                           /* [HGM] We seem to switch variant during a game!
4196                            * Presumably no holdings were displayed, so we have
4197                            * to move the position two files to the right to
4198                            * create room for them!
4199                            */
4200                           VariantClass newVariant;
4201                           switch(gameInfo.boardWidth) { // base guess on board width
4202                                 case 9:  newVariant = VariantShogi; break;
4203                                 case 10: newVariant = VariantGreat; break;
4204                                 default: newVariant = VariantCrazyhouse; break;
4205                           }
4206                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4207                           /* Get a move list just to see the header, which
4208                              will tell us whether this is really bug or zh */
4209                           if (ics_getting_history == H_FALSE) {
4210                             ics_getting_history = H_REQUESTED;
4211                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4212                             SendToICS(str);
4213                           }
4214                         }
4215                         new_piece[0] = NULLCHAR;
4216                         sscanf(parse, "game %d white [%s black [%s <- %s",
4217                                &gamenum, white_holding, black_holding,
4218                                new_piece);
4219                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4220                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4221                         /* [HGM] copy holdings to board holdings area */
4222                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4223                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4224                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4225 #if ZIPPY
4226                         if (appData.zippyPlay && first.initDone) {
4227                             ZippyHoldings(white_holding, black_holding,
4228                                           new_piece);
4229                         }
4230 #endif /*ZIPPY*/
4231                         if (tinyLayout || smallLayout) {
4232                             char wh[16], bh[16];
4233                             PackHolding(wh, white_holding);
4234                             PackHolding(bh, black_holding);
4235                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4236                                     gameInfo.white, gameInfo.black);
4237                         } else {
4238                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4239                                     gameInfo.white, white_holding, _("vs."),
4240                                     gameInfo.black, black_holding);
4241                         }
4242                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4243                         DrawPosition(FALSE, boards[currentMove]);
4244                         DisplayTitle(str);
4245                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4246                         sscanf(parse, "game %d white [%s black [%s <- %s",
4247                                &gamenum, white_holding, black_holding,
4248                                new_piece);
4249                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4250                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4251                         /* [HGM] copy holdings to partner-board holdings area */
4252                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4253                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4254                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4255                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4256                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4257                       }
4258                     }
4259                     /* Suppress following prompt */
4260                     if (looking_at(buf, &i, "*% ")) {
4261                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4262                         savingComment = FALSE;
4263                         suppressKibitz = 0;
4264                     }
4265                     next_out = i;
4266                 }
4267                 continue;
4268             }
4269
4270             i++;                /* skip unparsed character and loop back */
4271         }
4272
4273         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4274 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4275 //          SendToPlayer(&buf[next_out], i - next_out);
4276             started != STARTED_HOLDINGS && leftover_start > next_out) {
4277             SendToPlayer(&buf[next_out], leftover_start - next_out);
4278             next_out = i;
4279         }
4280
4281         leftover_len = buf_len - leftover_start;
4282         /* if buffer ends with something we couldn't parse,
4283            reparse it after appending the next read */
4284
4285     } else if (count == 0) {
4286         RemoveInputSource(isr);
4287         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4288     } else {
4289         DisplayFatalError(_("Error reading from ICS"), error, 1);
4290     }
4291 }
4292
4293
4294 /* Board style 12 looks like this:
4295
4296    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4297
4298  * The "<12> " is stripped before it gets to this routine.  The two
4299  * trailing 0's (flip state and clock ticking) are later addition, and
4300  * some chess servers may not have them, or may have only the first.
4301  * Additional trailing fields may be added in the future.
4302  */
4303
4304 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4305
4306 #define RELATION_OBSERVING_PLAYED    0
4307 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4308 #define RELATION_PLAYING_MYMOVE      1
4309 #define RELATION_PLAYING_NOTMYMOVE  -1
4310 #define RELATION_EXAMINING           2
4311 #define RELATION_ISOLATED_BOARD     -3
4312 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4313
4314 void
4315 ParseBoard12 (char *string)
4316 {
4317 #if ZIPPY
4318     int i, takeback;
4319     char *bookHit = NULL; // [HGM] book
4320 #endif
4321     GameMode newGameMode;
4322     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4323     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4324     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4325     char to_play, board_chars[200];
4326     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4327     char black[32], white[32];
4328     Board board;
4329     int prevMove = currentMove;
4330     int ticking = 2;
4331     ChessMove moveType;
4332     int fromX, fromY, toX, toY;
4333     char promoChar;
4334     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4335     Boolean weird = FALSE, reqFlag = FALSE;
4336
4337     fromX = fromY = toX = toY = -1;
4338
4339     newGame = FALSE;
4340
4341     if (appData.debugMode)
4342       fprintf(debugFP, "Parsing board: %s\n", string);
4343
4344     move_str[0] = NULLCHAR;
4345     elapsed_time[0] = NULLCHAR;
4346     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4347         int  i = 0, j;
4348         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4349             if(string[i] == ' ') { ranks++; files = 0; }
4350             else files++;
4351             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4352             i++;
4353         }
4354         for(j = 0; j <i; j++) board_chars[j] = string[j];
4355         board_chars[i] = '\0';
4356         string += i + 1;
4357     }
4358     n = sscanf(string, PATTERN, &to_play, &double_push,
4359                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4360                &gamenum, white, black, &relation, &basetime, &increment,
4361                &white_stren, &black_stren, &white_time, &black_time,
4362                &moveNum, str, elapsed_time, move_str, &ics_flip,
4363                &ticking);
4364
4365     if (n < 21) {
4366         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4367         DisplayError(str, 0);
4368         return;
4369     }
4370
4371     /* Convert the move number to internal form */
4372     moveNum = (moveNum - 1) * 2;
4373     if (to_play == 'B') moveNum++;
4374     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4375       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4376                         0, 1);
4377       return;
4378     }
4379
4380     switch (relation) {
4381       case RELATION_OBSERVING_PLAYED:
4382       case RELATION_OBSERVING_STATIC:
4383         if (gamenum == -1) {
4384             /* Old ICC buglet */
4385             relation = RELATION_OBSERVING_STATIC;
4386         }
4387         newGameMode = IcsObserving;
4388         break;
4389       case RELATION_PLAYING_MYMOVE:
4390       case RELATION_PLAYING_NOTMYMOVE:
4391         newGameMode =
4392           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4393             IcsPlayingWhite : IcsPlayingBlack;
4394         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4395         break;
4396       case RELATION_EXAMINING:
4397         newGameMode = IcsExamining;
4398         break;
4399       case RELATION_ISOLATED_BOARD:
4400       default:
4401         /* Just display this board.  If user was doing something else,
4402            we will forget about it until the next board comes. */
4403         newGameMode = IcsIdle;
4404         break;
4405       case RELATION_STARTING_POSITION:
4406         newGameMode = gameMode;
4407         break;
4408     }
4409
4410     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4411         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4412          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4413       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4414       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4415       static int lastBgGame = -1;
4416       char *toSqr;
4417       for (k = 0; k < ranks; k++) {
4418         for (j = 0; j < files; j++)
4419           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4420         if(gameInfo.holdingsWidth > 1) {
4421              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4422              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4423         }
4424       }
4425       CopyBoard(partnerBoard, board);
4426       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4427         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4428         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4429       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4430       if(toSqr = strchr(str, '-')) {
4431         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4432         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4433       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4434       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4435       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4436       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4437       if(twoBoards) {
4438           DisplayWhiteClock(white_time*fac, to_play == 'W');
4439           DisplayBlackClock(black_time*fac, to_play != 'W');
4440           activePartner = to_play;
4441           if(gamenum != lastBgGame) {
4442               char buf[MSG_SIZ];
4443               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4444               DisplayTitle(buf);
4445           }
4446           lastBgGame = gamenum;
4447           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4448                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4449       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4450                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4451       if(!twoBoards) DisplayMessage(partnerStatus, "");
4452         partnerBoardValid = TRUE;
4453       return;
4454     }
4455
4456     if(appData.dualBoard && appData.bgObserve) {
4457         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4458             SendToICS(ics_prefix), SendToICS("pobserve\n");
4459         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4460             char buf[MSG_SIZ];
4461             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4462             SendToICS(buf);
4463         }
4464     }
4465
4466     /* Modify behavior for initial board display on move listing
4467        of wild games.
4468        */
4469     switch (ics_getting_history) {
4470       case H_FALSE:
4471       case H_REQUESTED:
4472         break;
4473       case H_GOT_REQ_HEADER:
4474       case H_GOT_UNREQ_HEADER:
4475         /* This is the initial position of the current game */
4476         gamenum = ics_gamenum;
4477         moveNum = 0;            /* old ICS bug workaround */
4478         if (to_play == 'B') {
4479           startedFromSetupPosition = TRUE;
4480           blackPlaysFirst = TRUE;
4481           moveNum = 1;
4482           if (forwardMostMove == 0) forwardMostMove = 1;
4483           if (backwardMostMove == 0) backwardMostMove = 1;
4484           if (currentMove == 0) currentMove = 1;
4485         }
4486         newGameMode = gameMode;
4487         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4488         break;
4489       case H_GOT_UNWANTED_HEADER:
4490         /* This is an initial board that we don't want */
4491         return;
4492       case H_GETTING_MOVES:
4493         /* Should not happen */
4494         DisplayError(_("Error gathering move list: extra board"), 0);
4495         ics_getting_history = H_FALSE;
4496         return;
4497     }
4498
4499    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4500                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4501                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4502      /* [HGM] We seem to have switched variant unexpectedly
4503       * Try to guess new variant from board size
4504       */
4505           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4506           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4507           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4508           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4509           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4510           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4511           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4512           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4513           /* Get a move list just to see the header, which
4514              will tell us whether this is really bug or zh */
4515           if (ics_getting_history == H_FALSE) {
4516             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4517             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4518             SendToICS(str);
4519           }
4520     }
4521
4522     /* Take action if this is the first board of a new game, or of a
4523        different game than is currently being displayed.  */
4524     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4525         relation == RELATION_ISOLATED_BOARD) {
4526
4527         /* Forget the old game and get the history (if any) of the new one */
4528         if (gameMode != BeginningOfGame) {
4529           Reset(TRUE, TRUE);
4530         }
4531         newGame = TRUE;
4532         if (appData.autoRaiseBoard) BoardToTop();
4533         prevMove = -3;
4534         if (gamenum == -1) {
4535             newGameMode = IcsIdle;
4536         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4537                    appData.getMoveList && !reqFlag) {
4538             /* Need to get game history */
4539             ics_getting_history = H_REQUESTED;
4540             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4541             SendToICS(str);
4542         }
4543
4544         /* Initially flip the board to have black on the bottom if playing
4545            black or if the ICS flip flag is set, but let the user change
4546            it with the Flip View button. */
4547         flipView = appData.autoFlipView ?
4548           (newGameMode == IcsPlayingBlack) || ics_flip :
4549           appData.flipView;
4550
4551         /* Done with values from previous mode; copy in new ones */
4552         gameMode = newGameMode;
4553         ModeHighlight();
4554         ics_gamenum = gamenum;
4555         if (gamenum == gs_gamenum) {
4556             int klen = strlen(gs_kind);
4557             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4558             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4559             gameInfo.event = StrSave(str);
4560         } else {
4561             gameInfo.event = StrSave("ICS game");
4562         }
4563         gameInfo.site = StrSave(appData.icsHost);
4564         gameInfo.date = PGNDate();
4565         gameInfo.round = StrSave("-");
4566         gameInfo.white = StrSave(white);
4567         gameInfo.black = StrSave(black);
4568         timeControl = basetime * 60 * 1000;
4569         timeControl_2 = 0;
4570         timeIncrement = increment * 1000;
4571         movesPerSession = 0;
4572         gameInfo.timeControl = TimeControlTagValue();
4573         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4574   if (appData.debugMode) {
4575     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4576     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4577     setbuf(debugFP, NULL);
4578   }
4579
4580         gameInfo.outOfBook = NULL;
4581
4582         /* Do we have the ratings? */
4583         if (strcmp(player1Name, white) == 0 &&
4584             strcmp(player2Name, black) == 0) {
4585             if (appData.debugMode)
4586               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4587                       player1Rating, player2Rating);
4588             gameInfo.whiteRating = player1Rating;
4589             gameInfo.blackRating = player2Rating;
4590         } else if (strcmp(player2Name, white) == 0 &&
4591                    strcmp(player1Name, black) == 0) {
4592             if (appData.debugMode)
4593               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4594                       player2Rating, player1Rating);
4595             gameInfo.whiteRating = player2Rating;
4596             gameInfo.blackRating = player1Rating;
4597         }
4598         player1Name[0] = player2Name[0] = NULLCHAR;
4599
4600         /* Silence shouts if requested */
4601         if (appData.quietPlay &&
4602             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4603             SendToICS(ics_prefix);
4604             SendToICS("set shout 0\n");
4605         }
4606     }
4607
4608     /* Deal with midgame name changes */
4609     if (!newGame) {
4610         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4611             if (gameInfo.white) free(gameInfo.white);
4612             gameInfo.white = StrSave(white);
4613         }
4614         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4615             if (gameInfo.black) free(gameInfo.black);
4616             gameInfo.black = StrSave(black);
4617         }
4618     }
4619
4620     /* Throw away game result if anything actually changes in examine mode */
4621     if (gameMode == IcsExamining && !newGame) {
4622         gameInfo.result = GameUnfinished;
4623         if (gameInfo.resultDetails != NULL) {
4624             free(gameInfo.resultDetails);
4625             gameInfo.resultDetails = NULL;
4626         }
4627     }
4628
4629     /* In pausing && IcsExamining mode, we ignore boards coming
4630        in if they are in a different variation than we are. */
4631     if (pauseExamInvalid) return;
4632     if (pausing && gameMode == IcsExamining) {
4633         if (moveNum <= pauseExamForwardMostMove) {
4634             pauseExamInvalid = TRUE;
4635             forwardMostMove = pauseExamForwardMostMove;
4636             return;
4637         }
4638     }
4639
4640   if (appData.debugMode) {
4641     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4642   }
4643     /* Parse the board */
4644     for (k = 0; k < ranks; k++) {
4645       for (j = 0; j < files; j++)
4646         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4647       if(gameInfo.holdingsWidth > 1) {
4648            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4649            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4650       }
4651     }
4652     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4653       board[5][BOARD_RGHT+1] = WhiteAngel;
4654       board[6][BOARD_RGHT+1] = WhiteMarshall;
4655       board[1][0] = BlackMarshall;
4656       board[2][0] = BlackAngel;
4657       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4658     }
4659     CopyBoard(boards[moveNum], board);
4660     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4661     if (moveNum == 0) {
4662         startedFromSetupPosition =
4663           !CompareBoards(board, initialPosition);
4664         if(startedFromSetupPosition)
4665             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4666     }
4667
4668     /* [HGM] Set castling rights. Take the outermost Rooks,
4669        to make it also work for FRC opening positions. Note that board12
4670        is really defective for later FRC positions, as it has no way to
4671        indicate which Rook can castle if they are on the same side of King.
4672        For the initial position we grant rights to the outermost Rooks,
4673        and remember thos rights, and we then copy them on positions
4674        later in an FRC game. This means WB might not recognize castlings with
4675        Rooks that have moved back to their original position as illegal,
4676        but in ICS mode that is not its job anyway.
4677     */
4678     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4679     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4680
4681         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4682             if(board[0][i] == WhiteRook) j = i;
4683         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4684         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4685             if(board[0][i] == WhiteRook) j = i;
4686         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4687         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4688             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4689         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4690         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4691             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4692         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4693
4694         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4695         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4696         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4697             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4698         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4699             if(board[BOARD_HEIGHT-1][k] == bKing)
4700                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4701         if(gameInfo.variant == VariantTwoKings) {
4702             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4703             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4704             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4705         }
4706     } else { int r;
4707         r = boards[moveNum][CASTLING][0] = initialRights[0];
4708         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4709         r = boards[moveNum][CASTLING][1] = initialRights[1];
4710         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4711         r = boards[moveNum][CASTLING][3] = initialRights[3];
4712         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4713         r = boards[moveNum][CASTLING][4] = initialRights[4];
4714         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4715         /* wildcastle kludge: always assume King has rights */
4716         r = boards[moveNum][CASTLING][2] = initialRights[2];
4717         r = boards[moveNum][CASTLING][5] = initialRights[5];
4718     }
4719     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4720     boards[moveNum][EP_STATUS] = EP_NONE;
4721     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4722     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4723     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4724
4725
4726     if (ics_getting_history == H_GOT_REQ_HEADER ||
4727         ics_getting_history == H_GOT_UNREQ_HEADER) {
4728         /* This was an initial position from a move list, not
4729            the current position */
4730         return;
4731     }
4732
4733     /* Update currentMove and known move number limits */
4734     newMove = newGame || moveNum > forwardMostMove;
4735
4736     if (newGame) {
4737         forwardMostMove = backwardMostMove = currentMove = moveNum;
4738         if (gameMode == IcsExamining && moveNum == 0) {
4739           /* Workaround for ICS limitation: we are not told the wild
4740              type when starting to examine a game.  But if we ask for
4741              the move list, the move list header will tell us */
4742             ics_getting_history = H_REQUESTED;
4743             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4744             SendToICS(str);
4745         }
4746     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4747                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4748 #if ZIPPY
4749         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4750         /* [HGM] applied this also to an engine that is silently watching        */
4751         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4752             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4753             gameInfo.variant == currentlyInitializedVariant) {
4754           takeback = forwardMostMove - moveNum;
4755           for (i = 0; i < takeback; i++) {
4756             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4757             SendToProgram("undo\n", &first);
4758           }
4759         }
4760 #endif
4761
4762         forwardMostMove = moveNum;
4763         if (!pausing || currentMove > forwardMostMove)
4764           currentMove = forwardMostMove;
4765     } else {
4766         /* New part of history that is not contiguous with old part */
4767         if (pausing && gameMode == IcsExamining) {
4768             pauseExamInvalid = TRUE;
4769             forwardMostMove = pauseExamForwardMostMove;
4770             return;
4771         }
4772         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4773 #if ZIPPY
4774             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4775                 // [HGM] when we will receive the move list we now request, it will be
4776                 // fed to the engine from the first move on. So if the engine is not
4777                 // in the initial position now, bring it there.
4778                 InitChessProgram(&first, 0);
4779             }
4780 #endif
4781             ics_getting_history = H_REQUESTED;
4782             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4783             SendToICS(str);
4784         }
4785         forwardMostMove = backwardMostMove = currentMove = moveNum;
4786     }
4787
4788     /* Update the clocks */
4789     if (strchr(elapsed_time, '.')) {
4790       /* Time is in ms */
4791       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4792       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4793     } else {
4794       /* Time is in seconds */
4795       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4796       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4797     }
4798
4799
4800 #if ZIPPY
4801     if (appData.zippyPlay && newGame &&
4802         gameMode != IcsObserving && gameMode != IcsIdle &&
4803         gameMode != IcsExamining)
4804       ZippyFirstBoard(moveNum, basetime, increment);
4805 #endif
4806
4807     /* Put the move on the move list, first converting
4808        to canonical algebraic form. */
4809     if (moveNum > 0) {
4810   if (appData.debugMode) {
4811     int f = forwardMostMove;
4812     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4813             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4814             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4815     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4816     fprintf(debugFP, "moveNum = %d\n", moveNum);
4817     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4818     setbuf(debugFP, NULL);
4819   }
4820         if (moveNum <= backwardMostMove) {
4821             /* We don't know what the board looked like before
4822                this move.  Punt. */
4823           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4824             strcat(parseList[moveNum - 1], " ");
4825             strcat(parseList[moveNum - 1], elapsed_time);
4826             moveList[moveNum - 1][0] = NULLCHAR;
4827         } else if (strcmp(move_str, "none") == 0) {
4828             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4829             /* Again, we don't know what the board looked like;
4830                this is really the start of the game. */
4831             parseList[moveNum - 1][0] = NULLCHAR;
4832             moveList[moveNum - 1][0] = NULLCHAR;
4833             backwardMostMove = moveNum;
4834             startedFromSetupPosition = TRUE;
4835             fromX = fromY = toX = toY = -1;
4836         } else {
4837           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4838           //                 So we parse the long-algebraic move string in stead of the SAN move
4839           int valid; char buf[MSG_SIZ], *prom;
4840
4841           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4842                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4843           // str looks something like "Q/a1-a2"; kill the slash
4844           if(str[1] == '/')
4845             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4846           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4847           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4848                 strcat(buf, prom); // long move lacks promo specification!
4849           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4850                 if(appData.debugMode)
4851                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4852                 safeStrCpy(move_str, buf, MSG_SIZ);
4853           }
4854           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4855                                 &fromX, &fromY, &toX, &toY, &promoChar)
4856                || ParseOneMove(buf, moveNum - 1, &moveType,
4857                                 &fromX, &fromY, &toX, &toY, &promoChar);
4858           // end of long SAN patch
4859           if (valid) {
4860             (void) CoordsToAlgebraic(boards[moveNum - 1],
4861                                      PosFlags(moveNum - 1),
4862                                      fromY, fromX, toY, toX, promoChar,
4863                                      parseList[moveNum-1]);
4864             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4865               case MT_NONE:
4866               case MT_STALEMATE:
4867               default:
4868                 break;
4869               case MT_CHECK:
4870                 if(!IS_SHOGI(gameInfo.variant))
4871                     strcat(parseList[moveNum - 1], "+");
4872                 break;
4873               case MT_CHECKMATE:
4874               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4875                 strcat(parseList[moveNum - 1], "#");
4876                 break;
4877             }
4878             strcat(parseList[moveNum - 1], " ");
4879             strcat(parseList[moveNum - 1], elapsed_time);
4880             /* currentMoveString is set as a side-effect of ParseOneMove */
4881             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4882             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4883             strcat(moveList[moveNum - 1], "\n");
4884
4885             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4886                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4887               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4888                 ChessSquare old, new = boards[moveNum][k][j];
4889                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4890                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4891                   if(old == new) continue;
4892                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4893                   else if(new == WhiteWazir || new == BlackWazir) {
4894                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4895                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4896                       else boards[moveNum][k][j] = old; // preserve type of Gold
4897                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4898                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4899               }
4900           } else {
4901             /* Move from ICS was illegal!?  Punt. */
4902             if (appData.debugMode) {
4903               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4904               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4905             }
4906             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4907             strcat(parseList[moveNum - 1], " ");
4908             strcat(parseList[moveNum - 1], elapsed_time);
4909             moveList[moveNum - 1][0] = NULLCHAR;
4910             fromX = fromY = toX = toY = -1;
4911           }
4912         }
4913   if (appData.debugMode) {
4914     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4915     setbuf(debugFP, NULL);
4916   }
4917
4918 #if ZIPPY
4919         /* Send move to chess program (BEFORE animating it). */
4920         if (appData.zippyPlay && !newGame && newMove &&
4921            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4922
4923             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4924                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4925                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4926                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4927                             move_str);
4928                     DisplayError(str, 0);
4929                 } else {
4930                     if (first.sendTime) {
4931                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4932                     }
4933                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4934                     if (firstMove && !bookHit) {
4935                         firstMove = FALSE;
4936                         if (first.useColors) {
4937                           SendToProgram(gameMode == IcsPlayingWhite ?
4938                                         "white\ngo\n" :
4939                                         "black\ngo\n", &first);
4940                         } else {
4941                           SendToProgram("go\n", &first);
4942                         }
4943                         first.maybeThinking = TRUE;
4944                     }
4945                 }
4946             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4947               if (moveList[moveNum - 1][0] == NULLCHAR) {
4948                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4949                 DisplayError(str, 0);
4950               } else {
4951                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4952                 SendMoveToProgram(moveNum - 1, &first);
4953               }
4954             }
4955         }
4956 #endif
4957     }
4958
4959     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4960         /* If move comes from a remote source, animate it.  If it
4961            isn't remote, it will have already been animated. */
4962         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4963             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4964         }
4965         if (!pausing && appData.highlightLastMove) {
4966             SetHighlights(fromX, fromY, toX, toY);
4967         }
4968     }
4969
4970     /* Start the clocks */
4971     whiteFlag = blackFlag = FALSE;
4972     appData.clockMode = !(basetime == 0 && increment == 0);
4973     if (ticking == 0) {
4974       ics_clock_paused = TRUE;
4975       StopClocks();
4976     } else if (ticking == 1) {
4977       ics_clock_paused = FALSE;
4978     }
4979     if (gameMode == IcsIdle ||
4980         relation == RELATION_OBSERVING_STATIC ||
4981         relation == RELATION_EXAMINING ||
4982         ics_clock_paused)
4983       DisplayBothClocks();
4984     else
4985       StartClocks();
4986
4987     /* Display opponents and material strengths */
4988     if (gameInfo.variant != VariantBughouse &&
4989         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4990         if (tinyLayout || smallLayout) {
4991             if(gameInfo.variant == VariantNormal)
4992               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4993                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4994                     basetime, increment);
4995             else
4996               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4997                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4998                     basetime, increment, (int) gameInfo.variant);
4999         } else {
5000             if(gameInfo.variant == VariantNormal)
5001               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5002                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5003                     basetime, increment);
5004             else
5005               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5006                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5007                     basetime, increment, VariantName(gameInfo.variant));
5008         }
5009         DisplayTitle(str);
5010   if (appData.debugMode) {
5011     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5012   }
5013     }
5014
5015
5016     /* Display the board */
5017     if (!pausing && !appData.noGUI) {
5018
5019       if (appData.premove)
5020           if (!gotPremove ||
5021              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5022              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5023               ClearPremoveHighlights();
5024
5025       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5026         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5027       DrawPosition(j, boards[currentMove]);
5028
5029       DisplayMove(moveNum - 1);
5030       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5031             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5032               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5033         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5034       }
5035     }
5036
5037     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5038 #if ZIPPY
5039     if(bookHit) { // [HGM] book: simulate book reply
5040         static char bookMove[MSG_SIZ]; // a bit generous?
5041
5042         programStats.nodes = programStats.depth = programStats.time =
5043         programStats.score = programStats.got_only_move = 0;
5044         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5045
5046         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5047         strcat(bookMove, bookHit);
5048         HandleMachineMove(bookMove, &first);
5049     }
5050 #endif
5051 }
5052
5053 void
5054 GetMoveListEvent ()
5055 {
5056     char buf[MSG_SIZ];
5057     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5058         ics_getting_history = H_REQUESTED;
5059         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5060         SendToICS(buf);
5061     }
5062 }
5063
5064 void
5065 SendToBoth (char *msg)
5066 {   // to make it easy to keep two engines in step in dual analysis
5067     SendToProgram(msg, &first);
5068     if(second.analyzing) SendToProgram(msg, &second);
5069 }
5070
5071 void
5072 AnalysisPeriodicEvent (int force)
5073 {
5074     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5075          && !force) || !appData.periodicUpdates)
5076       return;
5077
5078     /* Send . command to Crafty to collect stats */
5079     SendToBoth(".\n");
5080
5081     /* Don't send another until we get a response (this makes
5082        us stop sending to old Crafty's which don't understand
5083        the "." command (sending illegal cmds resets node count & time,
5084        which looks bad)) */
5085     programStats.ok_to_send = 0;
5086 }
5087
5088 void
5089 ics_update_width (int new_width)
5090 {
5091         ics_printf("set width %d\n", new_width);
5092 }
5093
5094 void
5095 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5096 {
5097     char buf[MSG_SIZ];
5098
5099     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5100         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5101             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5102             SendToProgram(buf, cps);
5103             return;
5104         }
5105         // null move in variant where engine does not understand it (for analysis purposes)
5106         SendBoard(cps, moveNum + 1); // send position after move in stead.
5107         return;
5108     }
5109     if (cps->useUsermove) {
5110       SendToProgram("usermove ", cps);
5111     }
5112     if (cps->useSAN) {
5113       char *space;
5114       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5115         int len = space - parseList[moveNum];
5116         memcpy(buf, parseList[moveNum], len);
5117         buf[len++] = '\n';
5118         buf[len] = NULLCHAR;
5119       } else {
5120         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5121       }
5122       SendToProgram(buf, cps);
5123     } else {
5124       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5125         AlphaRank(moveList[moveNum], 4);
5126         SendToProgram(moveList[moveNum], cps);
5127         AlphaRank(moveList[moveNum], 4); // and back
5128       } else
5129       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5130        * the engine. It would be nice to have a better way to identify castle
5131        * moves here. */
5132       if(appData.fischerCastling && cps->useOOCastle) {
5133         int fromX = moveList[moveNum][0] - AAA;
5134         int fromY = moveList[moveNum][1] - ONE;
5135         int toX = moveList[moveNum][2] - AAA;
5136         int toY = moveList[moveNum][3] - ONE;
5137         if((boards[moveNum][fromY][fromX] == WhiteKing
5138             && boards[moveNum][toY][toX] == WhiteRook)
5139            || (boards[moveNum][fromY][fromX] == BlackKing
5140                && boards[moveNum][toY][toX] == BlackRook)) {
5141           if(toX > fromX) SendToProgram("O-O\n", cps);
5142           else SendToProgram("O-O-O\n", cps);
5143         }
5144         else SendToProgram(moveList[moveNum], cps);
5145       } else
5146       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5147         char *m = moveList[moveNum];
5148         if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
5149           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5150                                                m[2], m[3] - '0',
5151                                                m[5], m[6] - '0',
5152                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5153         else
5154           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5155                                                m[5], m[6] - '0',
5156                                                m[5], m[6] - '0',
5157                                                m[2], m[3] - '0');
5158           SendToProgram(buf, cps);
5159       } else
5160       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5161         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5162           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5163           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5164                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5165         } else
5166           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5167                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5168         SendToProgram(buf, cps);
5169       }
5170       else SendToProgram(moveList[moveNum], cps);
5171       /* End of additions by Tord */
5172     }
5173
5174     /* [HGM] setting up the opening has brought engine in force mode! */
5175     /*       Send 'go' if we are in a mode where machine should play. */
5176     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5177         (gameMode == TwoMachinesPlay   ||
5178 #if ZIPPY
5179          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5180 #endif
5181          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5182         SendToProgram("go\n", cps);
5183   if (appData.debugMode) {
5184     fprintf(debugFP, "(extra)\n");
5185   }
5186     }
5187     setboardSpoiledMachineBlack = 0;
5188 }
5189
5190 void
5191 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5192 {
5193     char user_move[MSG_SIZ];
5194     char suffix[4];
5195
5196     if(gameInfo.variant == VariantSChess && promoChar) {
5197         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5198         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5199     } else suffix[0] = NULLCHAR;
5200
5201     switch (moveType) {
5202       default:
5203         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5204                 (int)moveType, fromX, fromY, toX, toY);
5205         DisplayError(user_move + strlen("say "), 0);
5206         break;
5207       case WhiteKingSideCastle:
5208       case BlackKingSideCastle:
5209       case WhiteQueenSideCastleWild:
5210       case BlackQueenSideCastleWild:
5211       /* PUSH Fabien */
5212       case WhiteHSideCastleFR:
5213       case BlackHSideCastleFR:
5214       /* POP Fabien */
5215         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5216         break;
5217       case WhiteQueenSideCastle:
5218       case BlackQueenSideCastle:
5219       case WhiteKingSideCastleWild:
5220       case BlackKingSideCastleWild:
5221       /* PUSH Fabien */
5222       case WhiteASideCastleFR:
5223       case BlackASideCastleFR:
5224       /* POP Fabien */
5225         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5226         break;
5227       case WhiteNonPromotion:
5228       case BlackNonPromotion:
5229         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5230         break;
5231       case WhitePromotion:
5232       case BlackPromotion:
5233         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5234            gameInfo.variant == VariantMakruk)
5235           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5236                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5237                 PieceToChar(WhiteFerz));
5238         else if(gameInfo.variant == VariantGreat)
5239           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5240                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5241                 PieceToChar(WhiteMan));
5242         else
5243           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5244                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5245                 promoChar);
5246         break;
5247       case WhiteDrop:
5248       case BlackDrop:
5249       drop:
5250         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5251                  ToUpper(PieceToChar((ChessSquare) fromX)),
5252                  AAA + toX, ONE + toY);
5253         break;
5254       case IllegalMove:  /* could be a variant we don't quite understand */
5255         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5256       case NormalMove:
5257       case WhiteCapturesEnPassant:
5258       case BlackCapturesEnPassant:
5259         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5260                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5261         break;
5262     }
5263     SendToICS(user_move);
5264     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5265         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5266 }
5267
5268 void
5269 UploadGameEvent ()
5270 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5271     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5272     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5273     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5274       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5275       return;
5276     }
5277     if(gameMode != IcsExamining) { // is this ever not the case?
5278         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5279
5280         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5281           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5282         } else { // on FICS we must first go to general examine mode
5283           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5284         }
5285         if(gameInfo.variant != VariantNormal) {
5286             // try figure out wild number, as xboard names are not always valid on ICS
5287             for(i=1; i<=36; i++) {
5288               snprintf(buf, MSG_SIZ, "wild/%d", i);
5289                 if(StringToVariant(buf) == gameInfo.variant) break;
5290             }
5291             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5292             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5293             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5294         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5295         SendToICS(ics_prefix);
5296         SendToICS(buf);
5297         if(startedFromSetupPosition || backwardMostMove != 0) {
5298           fen = PositionToFEN(backwardMostMove, NULL, 1);
5299           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5300             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5301             SendToICS(buf);
5302           } else { // FICS: everything has to set by separate bsetup commands
5303             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5304             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5305             SendToICS(buf);
5306             if(!WhiteOnMove(backwardMostMove)) {
5307                 SendToICS("bsetup tomove black\n");
5308             }
5309             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5310             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5311             SendToICS(buf);
5312             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5313             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5314             SendToICS(buf);
5315             i = boards[backwardMostMove][EP_STATUS];
5316             if(i >= 0) { // set e.p.
5317               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5318                 SendToICS(buf);
5319             }
5320             bsetup++;
5321           }
5322         }
5323       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5324             SendToICS("bsetup done\n"); // switch to normal examining.
5325     }
5326     for(i = backwardMostMove; i<last; i++) {
5327         char buf[20];
5328         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5329         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5330             int len = strlen(moveList[i]);
5331             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5332             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5333         }
5334         SendToICS(buf);
5335     }
5336     SendToICS(ics_prefix);
5337     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5338 }
5339
5340 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5341 int legNr = 1;
5342
5343 void
5344 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5345 {
5346     if (rf == DROP_RANK) {
5347       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5348       sprintf(move, "%c@%c%c\n",
5349                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5350     } else {
5351         if (promoChar == 'x' || promoChar == NULLCHAR) {
5352           sprintf(move, "%c%c%c%c\n",
5353                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5354           if(killX >= 0 && killY >= 0) {
5355             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5356             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + killX, ONE + killY);
5357           }
5358         } else {
5359             sprintf(move, "%c%c%c%c%c\n",
5360                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5361         }
5362     }
5363 }
5364
5365 void
5366 ProcessICSInitScript (FILE *f)
5367 {
5368     char buf[MSG_SIZ];
5369
5370     while (fgets(buf, MSG_SIZ, f)) {
5371         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5372     }
5373
5374     fclose(f);
5375 }
5376
5377
5378 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5379 int dragging;
5380 static ClickType lastClickType;
5381
5382 int
5383 PieceInString (char *s, ChessSquare piece)
5384 {
5385   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5386   while((p = strchr(s, ID))) {
5387     if(!suffix || p[1] == suffix) return TRUE;
5388     s = p;
5389   }
5390   return FALSE;
5391 }
5392
5393 int
5394 Partner (ChessSquare *p)
5395 { // change piece into promotion partner if one shogi-promotes to the other
5396   ChessSquare partner = promoPartner[*p];
5397   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5398   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5399   *p = partner;
5400   return 1;
5401 }
5402
5403 void
5404 Sweep (int step)
5405 {
5406     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5407     static int toggleFlag;
5408     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5409     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5410     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5411     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5412     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5413     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5414     do {
5415         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5416         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5417         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5418         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5419         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5420         if(!step) step = -1;
5421     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5422             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5423             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5424             promoSweep == pawn ||
5425             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5426             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5427     if(toX >= 0) {
5428         int victim = boards[currentMove][toY][toX];
5429         boards[currentMove][toY][toX] = promoSweep;
5430         DrawPosition(FALSE, boards[currentMove]);
5431         boards[currentMove][toY][toX] = victim;
5432     } else
5433     ChangeDragPiece(promoSweep);
5434 }
5435
5436 int
5437 PromoScroll (int x, int y)
5438 {
5439   int step = 0;
5440
5441   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5442   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5443   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5444   if(!step) return FALSE;
5445   lastX = x; lastY = y;
5446   if((promoSweep < BlackPawn) == flipView) step = -step;
5447   if(step > 0) selectFlag = 1;
5448   if(!selectFlag) Sweep(step);
5449   return FALSE;
5450 }
5451
5452 void
5453 NextPiece (int step)
5454 {
5455     ChessSquare piece = boards[currentMove][toY][toX];
5456     do {
5457         pieceSweep -= step;
5458         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5459         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5460         if(!step) step = -1;
5461     } while(PieceToChar(pieceSweep) == '.');
5462     boards[currentMove][toY][toX] = pieceSweep;
5463     DrawPosition(FALSE, boards[currentMove]);
5464     boards[currentMove][toY][toX] = piece;
5465 }
5466 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5467 void
5468 AlphaRank (char *move, int n)
5469 {
5470 //    char *p = move, c; int x, y;
5471
5472     if (appData.debugMode) {
5473         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5474     }
5475
5476     if(move[1]=='*' &&
5477        move[2]>='0' && move[2]<='9' &&
5478        move[3]>='a' && move[3]<='x'    ) {
5479         move[1] = '@';
5480         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5481         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5482     } else
5483     if(move[0]>='0' && move[0]<='9' &&
5484        move[1]>='a' && move[1]<='x' &&
5485        move[2]>='0' && move[2]<='9' &&
5486        move[3]>='a' && move[3]<='x'    ) {
5487         /* input move, Shogi -> normal */
5488         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5489         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5490         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5491         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5492     } else
5493     if(move[1]=='@' &&
5494        move[3]>='0' && move[3]<='9' &&
5495        move[2]>='a' && move[2]<='x'    ) {
5496         move[1] = '*';
5497         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5498         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5499     } else
5500     if(
5501        move[0]>='a' && move[0]<='x' &&
5502        move[3]>='0' && move[3]<='9' &&
5503        move[2]>='a' && move[2]<='x'    ) {
5504          /* output move, normal -> Shogi */
5505         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5506         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5507         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5508         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5509         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5510     }
5511     if (appData.debugMode) {
5512         fprintf(debugFP, "   out = '%s'\n", move);
5513     }
5514 }
5515
5516 char yy_textstr[8000];
5517
5518 /* Parser for moves from gnuchess, ICS, or user typein box */
5519 Boolean
5520 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5521 {
5522     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5523
5524     switch (*moveType) {
5525       case WhitePromotion:
5526       case BlackPromotion:
5527       case WhiteNonPromotion:
5528       case BlackNonPromotion:
5529       case NormalMove:
5530       case FirstLeg:
5531       case WhiteCapturesEnPassant:
5532       case BlackCapturesEnPassant:
5533       case WhiteKingSideCastle:
5534       case WhiteQueenSideCastle:
5535       case BlackKingSideCastle:
5536       case BlackQueenSideCastle:
5537       case WhiteKingSideCastleWild:
5538       case WhiteQueenSideCastleWild:
5539       case BlackKingSideCastleWild:
5540       case BlackQueenSideCastleWild:
5541       /* Code added by Tord: */
5542       case WhiteHSideCastleFR:
5543       case WhiteASideCastleFR:
5544       case BlackHSideCastleFR:
5545       case BlackASideCastleFR:
5546       /* End of code added by Tord */
5547       case IllegalMove:         /* bug or odd chess variant */
5548         if(currentMoveString[1] == '@') { // illegal drop
5549           *fromX = WhiteOnMove(moveNum) ?
5550             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5551             (int) CharToPiece(ToLower(currentMoveString[0]));
5552           goto drop;
5553         }
5554         *fromX = currentMoveString[0] - AAA;
5555         *fromY = currentMoveString[1] - ONE;
5556         *toX = currentMoveString[2] - AAA;
5557         *toY = currentMoveString[3] - ONE;
5558         *promoChar = currentMoveString[4];
5559         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5560             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5561     if (appData.debugMode) {
5562         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5563     }
5564             *fromX = *fromY = *toX = *toY = 0;
5565             return FALSE;
5566         }
5567         if (appData.testLegality) {
5568           return (*moveType != IllegalMove);
5569         } else {
5570           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5571                          // [HGM] lion: if this is a double move we are less critical
5572                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5573         }
5574
5575       case WhiteDrop:
5576       case BlackDrop:
5577         *fromX = *moveType == WhiteDrop ?
5578           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5579           (int) CharToPiece(ToLower(currentMoveString[0]));
5580       drop:
5581         *fromY = DROP_RANK;
5582         *toX = currentMoveString[2] - AAA;
5583         *toY = currentMoveString[3] - ONE;
5584         *promoChar = NULLCHAR;
5585         return TRUE;
5586
5587       case AmbiguousMove:
5588       case ImpossibleMove:
5589       case EndOfFile:
5590       case ElapsedTime:
5591       case Comment:
5592       case PGNTag:
5593       case NAG:
5594       case WhiteWins:
5595       case BlackWins:
5596       case GameIsDrawn:
5597       default:
5598     if (appData.debugMode) {
5599         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5600     }
5601         /* bug? */
5602         *fromX = *fromY = *toX = *toY = 0;
5603         *promoChar = NULLCHAR;
5604         return FALSE;
5605     }
5606 }
5607
5608 Boolean pushed = FALSE;
5609 char *lastParseAttempt;
5610
5611 void
5612 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5613 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5614   int fromX, fromY, toX, toY; char promoChar;
5615   ChessMove moveType;
5616   Boolean valid;
5617   int nr = 0;
5618
5619   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5620   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5621     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5622     pushed = TRUE;
5623   }
5624   endPV = forwardMostMove;
5625   do {
5626     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5627     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5628     lastParseAttempt = pv;
5629     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5630     if(!valid && nr == 0 &&
5631        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5632         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5633         // Hande case where played move is different from leading PV move
5634         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5635         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5636         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5637         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5638           endPV += 2; // if position different, keep this
5639           moveList[endPV-1][0] = fromX + AAA;
5640           moveList[endPV-1][1] = fromY + ONE;
5641           moveList[endPV-1][2] = toX + AAA;
5642           moveList[endPV-1][3] = toY + ONE;
5643           parseList[endPV-1][0] = NULLCHAR;
5644           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5645         }
5646       }
5647     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5648     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5649     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5650     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5651         valid++; // allow comments in PV
5652         continue;
5653     }
5654     nr++;
5655     if(endPV+1 > framePtr) break; // no space, truncate
5656     if(!valid) break;
5657     endPV++;
5658     CopyBoard(boards[endPV], boards[endPV-1]);
5659     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5660     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5661     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5662     CoordsToAlgebraic(boards[endPV - 1],
5663                              PosFlags(endPV - 1),
5664                              fromY, fromX, toY, toX, promoChar,
5665                              parseList[endPV - 1]);
5666   } while(valid);
5667   if(atEnd == 2) return; // used hidden, for PV conversion
5668   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5669   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5670   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5671                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5672   DrawPosition(TRUE, boards[currentMove]);
5673 }
5674
5675 int
5676 MultiPV (ChessProgramState *cps, int kind)
5677 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5678         int i;
5679         for(i=0; i<cps->nrOptions; i++) {
5680             char *s = cps->option[i].name;
5681             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5682             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5683                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5684         }
5685         return -1;
5686 }
5687
5688 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5689 static int multi, pv_margin;
5690 static ChessProgramState *activeCps;
5691
5692 Boolean
5693 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5694 {
5695         int startPV, lineStart, origIndex = index;
5696         char *p, buf2[MSG_SIZ];
5697         ChessProgramState *cps = (pane ? &second : &first);
5698
5699         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5700         lastX = x; lastY = y;
5701         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5702         lineStart = startPV = index;
5703         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5704         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5705         index = startPV;
5706         do{ while(buf[index] && buf[index] != '\n') index++;
5707         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5708         buf[index] = 0;
5709         if(lineStart == 0 && gameMode == AnalyzeMode) {
5710             int n = 0;
5711             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5712             if(n == 0) { // click not on "fewer" or "more"
5713                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5714                     pv_margin = cps->option[multi].value;
5715                     activeCps = cps; // non-null signals margin adjustment
5716                 }
5717             } else if((multi = MultiPV(cps, 1)) >= 0) {
5718                 n += cps->option[multi].value; if(n < 1) n = 1;
5719                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5720                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5721                 cps->option[multi].value = n;
5722                 *start = *end = 0;
5723                 return FALSE;
5724             }
5725         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5726                 ExcludeClick(origIndex - lineStart);
5727                 return FALSE;
5728         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5729                 Collapse(origIndex - lineStart);
5730                 return FALSE;
5731         }
5732         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5733         *start = startPV; *end = index-1;
5734         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5735         return TRUE;
5736 }
5737
5738 char *
5739 PvToSAN (char *pv)
5740 {
5741         static char buf[10*MSG_SIZ];
5742         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5743         *buf = NULLCHAR;
5744         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5745         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5746         for(i = forwardMostMove; i<endPV; i++){
5747             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5748             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5749             k += strlen(buf+k);
5750         }
5751         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5752         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5753         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5754         endPV = savedEnd;
5755         return buf;
5756 }
5757
5758 Boolean
5759 LoadPV (int x, int y)
5760 { // called on right mouse click to load PV
5761   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5762   lastX = x; lastY = y;
5763   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5764   extendGame = FALSE;
5765   return TRUE;
5766 }
5767
5768 void
5769 UnLoadPV ()
5770 {
5771   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5772   if(activeCps) {
5773     if(pv_margin != activeCps->option[multi].value) {
5774       char buf[MSG_SIZ];
5775       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5776       SendToProgram(buf, activeCps);
5777       activeCps->option[multi].value = pv_margin;
5778     }
5779     activeCps = NULL;
5780     return;
5781   }
5782   if(endPV < 0) return;
5783   if(appData.autoCopyPV) CopyFENToClipboard();
5784   endPV = -1;
5785   if(extendGame && currentMove > forwardMostMove) {
5786         Boolean saveAnimate = appData.animate;
5787         if(pushed) {
5788             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5789                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5790             } else storedGames--; // abandon shelved tail of original game
5791         }
5792         pushed = FALSE;
5793         forwardMostMove = currentMove;
5794         currentMove = oldFMM;
5795         appData.animate = FALSE;
5796         ToNrEvent(forwardMostMove);
5797         appData.animate = saveAnimate;
5798   }
5799   currentMove = forwardMostMove;
5800   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5801   ClearPremoveHighlights();
5802   DrawPosition(TRUE, boards[currentMove]);
5803 }
5804
5805 void
5806 MovePV (int x, int y, int h)
5807 { // step through PV based on mouse coordinates (called on mouse move)
5808   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5809
5810   if(activeCps) { // adjusting engine's multi-pv margin
5811     if(x > lastX) pv_margin++; else
5812     if(x < lastX) pv_margin -= (pv_margin > 0);
5813     if(x != lastX) {
5814       char buf[MSG_SIZ];
5815       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5816       DisplayMessage(buf, "");
5817     }
5818     lastX = x;
5819     return;
5820   }
5821   // we must somehow check if right button is still down (might be released off board!)
5822   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5823   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5824   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5825   if(!step) return;
5826   lastX = x; lastY = y;
5827
5828   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5829   if(endPV < 0) return;
5830   if(y < margin) step = 1; else
5831   if(y > h - margin) step = -1;
5832   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5833   currentMove += step;
5834   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5835   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5836                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5837   DrawPosition(FALSE, boards[currentMove]);
5838 }
5839
5840
5841 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5842 // All positions will have equal probability, but the current method will not provide a unique
5843 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5844 #define DARK 1
5845 #define LITE 2
5846 #define ANY 3
5847
5848 int squaresLeft[4];
5849 int piecesLeft[(int)BlackPawn];
5850 int seed, nrOfShuffles;
5851
5852 void
5853 GetPositionNumber ()
5854 {       // sets global variable seed
5855         int i;
5856
5857         seed = appData.defaultFrcPosition;
5858         if(seed < 0) { // randomize based on time for negative FRC position numbers
5859                 for(i=0; i<50; i++) seed += random();
5860                 seed = random() ^ random() >> 8 ^ random() << 8;
5861                 if(seed<0) seed = -seed;
5862         }
5863 }
5864
5865 int
5866 put (Board board, int pieceType, int rank, int n, int shade)
5867 // put the piece on the (n-1)-th empty squares of the given shade
5868 {
5869         int i;
5870
5871         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5872                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5873                         board[rank][i] = (ChessSquare) pieceType;
5874                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5875                         squaresLeft[ANY]--;
5876                         piecesLeft[pieceType]--;
5877                         return i;
5878                 }
5879         }
5880         return -1;
5881 }
5882
5883
5884 void
5885 AddOnePiece (Board board, int pieceType, int rank, int shade)
5886 // calculate where the next piece goes, (any empty square), and put it there
5887 {
5888         int i;
5889
5890         i = seed % squaresLeft[shade];
5891         nrOfShuffles *= squaresLeft[shade];
5892         seed /= squaresLeft[shade];
5893         put(board, pieceType, rank, i, shade);
5894 }
5895
5896 void
5897 AddTwoPieces (Board board, int pieceType, int rank)
5898 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5899 {
5900         int i, n=squaresLeft[ANY], j=n-1, k;
5901
5902         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5903         i = seed % k;  // pick one
5904         nrOfShuffles *= k;
5905         seed /= k;
5906         while(i >= j) i -= j--;
5907         j = n - 1 - j; i += j;
5908         put(board, pieceType, rank, j, ANY);
5909         put(board, pieceType, rank, i, ANY);
5910 }
5911
5912 void
5913 SetUpShuffle (Board board, int number)
5914 {
5915         int i, p, first=1;
5916
5917         GetPositionNumber(); nrOfShuffles = 1;
5918
5919         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5920         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5921         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5922
5923         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5924
5925         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5926             p = (int) board[0][i];
5927             if(p < (int) BlackPawn) piecesLeft[p] ++;
5928             board[0][i] = EmptySquare;
5929         }
5930
5931         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5932             // shuffles restricted to allow normal castling put KRR first
5933             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5934                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5935             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5936                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5937             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5938                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5939             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5940                 put(board, WhiteRook, 0, 0, ANY);
5941             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5942         }
5943
5944         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5945             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5946             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5947                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5948                 while(piecesLeft[p] >= 2) {
5949                     AddOnePiece(board, p, 0, LITE);
5950                     AddOnePiece(board, p, 0, DARK);
5951                 }
5952                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5953             }
5954
5955         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5956             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5957             // but we leave King and Rooks for last, to possibly obey FRC restriction
5958             if(p == (int)WhiteRook) continue;
5959             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5960             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5961         }
5962
5963         // now everything is placed, except perhaps King (Unicorn) and Rooks
5964
5965         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5966             // Last King gets castling rights
5967             while(piecesLeft[(int)WhiteUnicorn]) {
5968                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5969                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5970             }
5971
5972             while(piecesLeft[(int)WhiteKing]) {
5973                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5974                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5975             }
5976
5977
5978         } else {
5979             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5980             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5981         }
5982
5983         // Only Rooks can be left; simply place them all
5984         while(piecesLeft[(int)WhiteRook]) {
5985                 i = put(board, WhiteRook, 0, 0, ANY);
5986                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5987                         if(first) {
5988                                 first=0;
5989                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5990                         }
5991                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5992                 }
5993         }
5994         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5995             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5996         }
5997
5998         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5999 }
6000
6001 int
6002 ptclen (const char *s, char *escapes)
6003 {
6004     int n = 0;
6005     if(!*escapes) return strlen(s);
6006     while(*s) n += (*s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)), s++;
6007     return n;
6008 }
6009
6010 static int pieceOrder[] = {
6011   0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, // P N B R Q F E A C W M
6012  11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, // O H I J G D V L S U Lion
6013  45, 23, 24, 25, 26, 27, 28, 29, 46, 31, 32, // Sword Zebra Camel Tower Wolf Dragon Duck Axe Leopard Gnu Cub
6014  44, 51, 56, 57, 58, 59, 60, 61, 62, 63, 34, // Whale Pegasus Wizard Copper Iron Viking Flag Amazon Wheel Shield Claw
6015  33, 55, 53, 42, 37, 48, 39, 40, 41, 22, 30, // +P +N =B =R +L +S +E +Ph +Kn Butterfly Hat
6016  38, 43, 35, 36, 49, 47, 52, 50, 54, 64, 65 // +V +M =H =D Princess HSword +GB HCrown Wheer Shierd King
6017 };
6018
6019 int
6020 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6021 /* [HGM] moved here from winboard.c because of its general usefulness */
6022 /*       Basically a safe strcpy that uses the last character as King */
6023 {
6024     int result = FALSE; int NrPieces;
6025     unsigned char partner[EmptySquare];
6026
6027     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6028                     && NrPieces >= 12 && !(NrPieces&1)) {
6029         int i, ii, j = 0; /* [HGM] Accept even length from 12 to 88 */
6030
6031         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6032         for( ii=0; ii<NrPieces/2-1; ii++ ) {
6033             char *p, c=0;
6034             i = pieceOrder[ii];
6035             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6036             table[i] = map[j++];
6037             if(p = strchr(escapes, map[j])) j++, table[i] += 64*(p - escapes + 1);
6038             if(c) partner[i] = table[i], table[i] = c;
6039         }
6040         table[(int) WhiteKing]  = map[j++];
6041         for( ii=0; ii<NrPieces/2-1; ii++ ) {
6042             char *p, c=0;
6043             i = WHITE_TO_BLACK pieceOrder[ii];
6044             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6045             table[i] = map[j++];
6046             if(p = strchr(escapes, map[j])) j++, table[i] += 64*(p - escapes + 1);
6047             if(c) partner[i] = table[i], table[i] = c;
6048         }
6049         table[(int) BlackKing]  = map[j++];
6050
6051
6052         if(*escapes) { // set up promotion pairing
6053             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6054             // pieceToChar entirely filled, so we can look up specified partners
6055             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6056                 int c = table[i];
6057                 if(c == '^' || c == '-') { // has specified partner
6058                     int p;
6059                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6060                     if(c == '^') table[i] = '+';
6061                     if(p < EmptySquare) promoPartner[p] = i, promoPartner[i] = p; // marry them
6062                 } else if(c == '*') {
6063                     table[i] = partner[i];
6064                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6065                 }
6066             }
6067         }
6068
6069         result = TRUE;
6070     }
6071
6072     return result;
6073 }
6074
6075 int
6076 SetCharTable (unsigned char *table, const char * map)
6077 {
6078     return SetCharTableEsc(table, map, "");
6079 }
6080
6081 void
6082 Prelude (Board board)
6083 {       // [HGM] superchess: random selection of exo-pieces
6084         int i, j, k; ChessSquare p;
6085         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6086
6087         GetPositionNumber(); // use FRC position number
6088
6089         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6090             SetCharTable(pieceToChar, appData.pieceToCharTable);
6091             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6092                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6093         }
6094
6095         j = seed%4;                 seed /= 4;
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 >= j); seed /= 3;
6100         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = 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%3;                 seed /= 3;
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%2 + (seed%2 >= j); seed /= 2;
6108         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6109         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6110         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6111         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6112         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6113         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6114         put(board, exoPieces[0],    0, 0, ANY);
6115         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6116 }
6117
6118 void
6119 InitPosition (int redraw)
6120 {
6121     ChessSquare (* pieces)[BOARD_FILES];
6122     int i, j, pawnRow=1, pieceRows=1, overrule,
6123     oldx = gameInfo.boardWidth,
6124     oldy = gameInfo.boardHeight,
6125     oldh = gameInfo.holdingsWidth;
6126     static int oldv;
6127
6128     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6129
6130     /* [AS] Initialize pv info list [HGM] and game status */
6131     {
6132         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6133             pvInfoList[i].depth = 0;
6134             boards[i][EP_STATUS] = EP_NONE;
6135             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6136         }
6137
6138         initialRulePlies = 0; /* 50-move counter start */
6139
6140         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6141         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6142     }
6143
6144
6145     /* [HGM] logic here is completely changed. In stead of full positions */
6146     /* the initialized data only consist of the two backranks. The switch */
6147     /* selects which one we will use, which is than copied to the Board   */
6148     /* initialPosition, which for the rest is initialized by Pawns and    */
6149     /* empty squares. This initial position is then copied to boards[0],  */
6150     /* possibly after shuffling, so that it remains available.            */
6151
6152     gameInfo.holdingsWidth = 0; /* default board sizes */
6153     gameInfo.boardWidth    = 8;
6154     gameInfo.boardHeight   = 8;
6155     gameInfo.holdingsSize  = 0;
6156     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6157     for(i=0; i<BOARD_FILES-6; i++)
6158       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6159     initialPosition[EP_STATUS] = EP_NONE;
6160     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6161     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6162     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6163          SetCharTable(pieceNickName, appData.pieceNickNames);
6164     else SetCharTable(pieceNickName, "............");
6165     pieces = FIDEArray;
6166
6167     switch (gameInfo.variant) {
6168     case VariantFischeRandom:
6169       shuffleOpenings = TRUE;
6170       appData.fischerCastling = TRUE;
6171     default:
6172       break;
6173     case VariantShatranj:
6174       pieces = ShatranjArray;
6175       nrCastlingRights = 0;
6176       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6177       break;
6178     case VariantMakruk:
6179       pieces = makrukArray;
6180       nrCastlingRights = 0;
6181       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6182       break;
6183     case VariantASEAN:
6184       pieces = aseanArray;
6185       nrCastlingRights = 0;
6186       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6187       break;
6188     case VariantTwoKings:
6189       pieces = twoKingsArray;
6190       break;
6191     case VariantGrand:
6192       pieces = GrandArray;
6193       nrCastlingRights = 0;
6194       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6195       gameInfo.boardWidth = 10;
6196       gameInfo.boardHeight = 10;
6197       gameInfo.holdingsSize = 7;
6198       break;
6199     case VariantCapaRandom:
6200       shuffleOpenings = TRUE;
6201       appData.fischerCastling = TRUE;
6202     case VariantCapablanca:
6203       pieces = CapablancaArray;
6204       gameInfo.boardWidth = 10;
6205       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6206       break;
6207     case VariantGothic:
6208       pieces = GothicArray;
6209       gameInfo.boardWidth = 10;
6210       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6211       break;
6212     case VariantSChess:
6213       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6214       gameInfo.holdingsSize = 7;
6215       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6216       break;
6217     case VariantJanus:
6218       pieces = JanusArray;
6219       gameInfo.boardWidth = 10;
6220       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6221       nrCastlingRights = 6;
6222         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6223         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6224         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6225         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6226         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6227         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6228       break;
6229     case VariantFalcon:
6230       pieces = FalconArray;
6231       gameInfo.boardWidth = 10;
6232       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6233       break;
6234     case VariantXiangqi:
6235       pieces = XiangqiArray;
6236       gameInfo.boardWidth  = 9;
6237       gameInfo.boardHeight = 10;
6238       nrCastlingRights = 0;
6239       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6240       break;
6241     case VariantShogi:
6242       pieces = ShogiArray;
6243       gameInfo.boardWidth  = 9;
6244       gameInfo.boardHeight = 9;
6245       gameInfo.holdingsSize = 7;
6246       nrCastlingRights = 0;
6247       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6248       break;
6249     case VariantChu:
6250       pieces = ChuArray; pieceRows = 3;
6251       gameInfo.boardWidth  = 12;
6252       gameInfo.boardHeight = 12;
6253       nrCastlingRights = 0;
6254       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN/^P.^B^R.^S^E^X^O^G^C^A^T^H^D.^V^M^L^I^FK"
6255                                    "p.brqsexogcathd.vmlifn/^p.^b^r.^s^e^x^o^g^c^a^t^h^d.^v^m^l^i^fk", SUFFIXES);
6256       break;
6257     case VariantCourier:
6258       pieces = CourierArray;
6259       gameInfo.boardWidth  = 12;
6260       nrCastlingRights = 0;
6261       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6262       break;
6263     case VariantKnightmate:
6264       pieces = KnightmateArray;
6265       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6266       break;
6267     case VariantSpartan:
6268       pieces = SpartanArray;
6269       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6270       break;
6271     case VariantLion:
6272       pieces = lionArray;
6273       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6274       break;
6275     case VariantChuChess:
6276       pieces = ChuChessArray;
6277       gameInfo.boardWidth = 10;
6278       gameInfo.boardHeight = 10;
6279       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6280       break;
6281     case VariantFairy:
6282       pieces = fairyArray;
6283       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6284       break;
6285     case VariantGreat:
6286       pieces = GreatArray;
6287       gameInfo.boardWidth = 10;
6288       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6289       gameInfo.holdingsSize = 8;
6290       break;
6291     case VariantSuper:
6292       pieces = FIDEArray;
6293       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6294       gameInfo.holdingsSize = 8;
6295       startedFromSetupPosition = TRUE;
6296       break;
6297     case VariantCrazyhouse:
6298     case VariantBughouse:
6299       pieces = FIDEArray;
6300       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6301       gameInfo.holdingsSize = 5;
6302       break;
6303     case VariantWildCastle:
6304       pieces = FIDEArray;
6305       /* !!?shuffle with kings guaranteed to be on d or e file */
6306       shuffleOpenings = 1;
6307       break;
6308     case VariantNoCastle:
6309       pieces = FIDEArray;
6310       nrCastlingRights = 0;
6311       /* !!?unconstrained back-rank shuffle */
6312       shuffleOpenings = 1;
6313       break;
6314     }
6315
6316     overrule = 0;
6317     if(appData.NrFiles >= 0) {
6318         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6319         gameInfo.boardWidth = appData.NrFiles;
6320     }
6321     if(appData.NrRanks >= 0) {
6322         gameInfo.boardHeight = appData.NrRanks;
6323     }
6324     if(appData.holdingsSize >= 0) {
6325         i = appData.holdingsSize;
6326         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6327         gameInfo.holdingsSize = i;
6328     }
6329     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6330     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6331         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6332
6333     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6334     if(pawnRow < 1) pawnRow = 1;
6335     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6336        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6337     if(gameInfo.variant == VariantChu) pawnRow = 3;
6338
6339     /* User pieceToChar list overrules defaults */
6340     if(appData.pieceToCharTable != NULL)
6341         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6342
6343     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6344
6345         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6346             s = (ChessSquare) 0; /* account holding counts in guard band */
6347         for( i=0; i<BOARD_HEIGHT; i++ )
6348             initialPosition[i][j] = s;
6349
6350         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6351         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6352         initialPosition[pawnRow][j] = WhitePawn;
6353         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6354         if(gameInfo.variant == VariantXiangqi) {
6355             if(j&1) {
6356                 initialPosition[pawnRow][j] =
6357                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6358                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6359                    initialPosition[2][j] = WhiteCannon;
6360                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6361                 }
6362             }
6363         }
6364         if(gameInfo.variant == VariantChu) {
6365              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6366                initialPosition[pawnRow+1][j] = WhiteCobra,
6367                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6368              for(i=1; i<pieceRows; i++) {
6369                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6370                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6371              }
6372         }
6373         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6374             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6375                initialPosition[0][j] = WhiteRook;
6376                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6377             }
6378         }
6379         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6380     }
6381     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6382     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6383
6384             j=BOARD_LEFT+1;
6385             initialPosition[1][j] = WhiteBishop;
6386             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6387             j=BOARD_RGHT-2;
6388             initialPosition[1][j] = WhiteRook;
6389             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6390     }
6391
6392     if( nrCastlingRights == -1) {
6393         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6394         /*       This sets default castling rights from none to normal corners   */
6395         /* Variants with other castling rights must set them themselves above    */
6396         nrCastlingRights = 6;
6397
6398         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6399         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6400         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6401         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6402         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6403         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6404      }
6405
6406      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6407      if(gameInfo.variant == VariantGreat) { // promotion commoners
6408         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6409         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6410         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6411         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6412      }
6413      if( gameInfo.variant == VariantSChess ) {
6414       initialPosition[1][0] = BlackMarshall;
6415       initialPosition[2][0] = BlackAngel;
6416       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6417       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6418       initialPosition[1][1] = initialPosition[2][1] =
6419       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6420      }
6421   if (appData.debugMode) {
6422     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6423   }
6424     if(shuffleOpenings) {
6425         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6426         startedFromSetupPosition = TRUE;
6427     }
6428     if(startedFromPositionFile) {
6429       /* [HGM] loadPos: use PositionFile for every new game */
6430       CopyBoard(initialPosition, filePosition);
6431       for(i=0; i<nrCastlingRights; i++)
6432           initialRights[i] = filePosition[CASTLING][i];
6433       startedFromSetupPosition = TRUE;
6434     }
6435
6436     CopyBoard(boards[0], initialPosition);
6437
6438     if(oldx != gameInfo.boardWidth ||
6439        oldy != gameInfo.boardHeight ||
6440        oldv != gameInfo.variant ||
6441        oldh != gameInfo.holdingsWidth
6442                                          )
6443             InitDrawingSizes(-2 ,0);
6444
6445     oldv = gameInfo.variant;
6446     if (redraw)
6447       DrawPosition(TRUE, boards[currentMove]);
6448 }
6449
6450 void
6451 SendBoard (ChessProgramState *cps, int moveNum)
6452 {
6453     char message[MSG_SIZ];
6454
6455     if (cps->useSetboard) {
6456       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6457       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6458       SendToProgram(message, cps);
6459       free(fen);
6460
6461     } else {
6462       ChessSquare *bp;
6463       int i, j, left=0, right=BOARD_WIDTH;
6464       /* Kludge to set black to move, avoiding the troublesome and now
6465        * deprecated "black" command.
6466        */
6467       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6468         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6469
6470       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6471
6472       SendToProgram("edit\n", cps);
6473       SendToProgram("#\n", cps);
6474       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6475         bp = &boards[moveNum][i][left];
6476         for (j = left; j < right; j++, bp++) {
6477           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6478           if ((int) *bp < (int) BlackPawn) {
6479             if(j == BOARD_RGHT+1)
6480                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6481             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6482             if(message[0] == '+' || message[0] == '~') {
6483               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6484                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6485                         AAA + j, ONE + i - '0');
6486             }
6487             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6488                 message[1] = BOARD_RGHT   - 1 - j + '1';
6489                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6490             }
6491             SendToProgram(message, cps);
6492           }
6493         }
6494       }
6495
6496       SendToProgram("c\n", cps);
6497       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6498         bp = &boards[moveNum][i][left];
6499         for (j = left; j < right; j++, bp++) {
6500           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6501           if (((int) *bp != (int) EmptySquare)
6502               && ((int) *bp >= (int) BlackPawn)) {
6503             if(j == BOARD_LEFT-2)
6504                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6505             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6506                     AAA + j, ONE + i - '0');
6507             if(message[0] == '+' || message[0] == '~') {
6508               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6509                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6510                         AAA + j, ONE + i - '0');
6511             }
6512             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6513                 message[1] = BOARD_RGHT   - 1 - j + '1';
6514                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6515             }
6516             SendToProgram(message, cps);
6517           }
6518         }
6519       }
6520
6521       SendToProgram(".\n", cps);
6522     }
6523     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6524 }
6525
6526 char exclusionHeader[MSG_SIZ];
6527 int exCnt, excludePtr;
6528 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6529 static Exclusion excluTab[200];
6530 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6531
6532 static void
6533 WriteMap (int s)
6534 {
6535     int j;
6536     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6537     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6538 }
6539
6540 static void
6541 ClearMap ()
6542 {
6543     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6544     excludePtr = 24; exCnt = 0;
6545     WriteMap(0);
6546 }
6547
6548 static void
6549 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6550 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6551     char buf[2*MOVE_LEN], *p;
6552     Exclusion *e = excluTab;
6553     int i;
6554     for(i=0; i<exCnt; i++)
6555         if(e[i].ff == fromX && e[i].fr == fromY &&
6556            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6557     if(i == exCnt) { // was not in exclude list; add it
6558         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6559         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6560             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6561             return; // abort
6562         }
6563         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6564         excludePtr++; e[i].mark = excludePtr++;
6565         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6566         exCnt++;
6567     }
6568     exclusionHeader[e[i].mark] = state;
6569 }
6570
6571 static int
6572 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6573 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6574     char buf[MSG_SIZ];
6575     int j, k;
6576     ChessMove moveType;
6577     if((signed char)promoChar == -1) { // kludge to indicate best move
6578         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6579             return 1; // if unparsable, abort
6580     }
6581     // update exclusion map (resolving toggle by consulting existing state)
6582     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6583     j = k%8; k >>= 3;
6584     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6585     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6586          excludeMap[k] |=   1<<j;
6587     else excludeMap[k] &= ~(1<<j);
6588     // update header
6589     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6590     // inform engine
6591     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6592     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6593     SendToBoth(buf);
6594     return (state == '+');
6595 }
6596
6597 static void
6598 ExcludeClick (int index)
6599 {
6600     int i, j;
6601     Exclusion *e = excluTab;
6602     if(index < 25) { // none, best or tail clicked
6603         if(index < 13) { // none: include all
6604             WriteMap(0); // clear map
6605             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6606             SendToBoth("include all\n"); // and inform engine
6607         } else if(index > 18) { // tail
6608             if(exclusionHeader[19] == '-') { // tail was excluded
6609                 SendToBoth("include all\n");
6610                 WriteMap(0); // clear map completely
6611                 // now re-exclude selected moves
6612                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6613                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6614             } else { // tail was included or in mixed state
6615                 SendToBoth("exclude all\n");
6616                 WriteMap(0xFF); // fill map completely
6617                 // now re-include selected moves
6618                 j = 0; // count them
6619                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6620                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6621                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6622             }
6623         } else { // best
6624             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6625         }
6626     } else {
6627         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6628             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6629             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6630             break;
6631         }
6632     }
6633 }
6634
6635 ChessSquare
6636 DefaultPromoChoice (int white)
6637 {
6638     ChessSquare result;
6639     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6640        gameInfo.variant == VariantMakruk)
6641         result = WhiteFerz; // no choice
6642     else if(gameInfo.variant == VariantASEAN)
6643         result = WhiteRook; // no choice
6644     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6645         result= WhiteKing; // in Suicide Q is the last thing we want
6646     else if(gameInfo.variant == VariantSpartan)
6647         result = white ? WhiteQueen : WhiteAngel;
6648     else result = WhiteQueen;
6649     if(!white) result = WHITE_TO_BLACK result;
6650     return result;
6651 }
6652
6653 static int autoQueen; // [HGM] oneclick
6654
6655 int
6656 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6657 {
6658     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6659     /* [HGM] add Shogi promotions */
6660     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6661     ChessSquare piece, partner;
6662     ChessMove moveType;
6663     Boolean premove;
6664
6665     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6666     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6667
6668     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6669       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6670         return FALSE;
6671
6672     piece = boards[currentMove][fromY][fromX];
6673     if(gameInfo.variant == VariantChu) {
6674         promotionZoneSize = BOARD_HEIGHT/3;
6675         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6676     } else if(gameInfo.variant == VariantShogi) {
6677         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6678         highestPromotingPiece = (int)WhiteAlfil;
6679     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6680         promotionZoneSize = 3;
6681     }
6682
6683     // Treat Lance as Pawn when it is not representing Amazon or Lance
6684     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6685         if(piece == WhiteLance) piece = WhitePawn; else
6686         if(piece == BlackLance) piece = BlackPawn;
6687     }
6688
6689     // next weed out all moves that do not touch the promotion zone at all
6690     if((int)piece >= BlackPawn) {
6691         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6692              return FALSE;
6693         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6694         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6695     } else {
6696         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6697            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6698         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6699              return FALSE;
6700     }
6701
6702     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6703
6704     // weed out mandatory Shogi promotions
6705     if(gameInfo.variant == VariantShogi) {
6706         if(piece >= BlackPawn) {
6707             if(toY == 0 && piece == BlackPawn ||
6708                toY == 0 && piece == BlackQueen ||
6709                toY <= 1 && piece == BlackKnight) {
6710                 *promoChoice = '+';
6711                 return FALSE;
6712             }
6713         } else {
6714             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6715                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6716                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6717                 *promoChoice = '+';
6718                 return FALSE;
6719             }
6720         }
6721     }
6722
6723     // weed out obviously illegal Pawn moves
6724     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6725         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6726         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6727         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6728         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6729         // note we are not allowed to test for valid (non-)capture, due to premove
6730     }
6731
6732     // we either have a choice what to promote to, or (in Shogi) whether to promote
6733     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6734        gameInfo.variant == VariantMakruk) {
6735         ChessSquare p=BlackFerz;  // no choice
6736         while(p < EmptySquare) {  //but make sure we use piece that exists
6737             *promoChoice = PieceToChar(p++);
6738             if(*promoChoice != '.') break;
6739         }
6740         return FALSE;
6741     }
6742     // no sense asking what we must promote to if it is going to explode...
6743     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6744         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6745         return FALSE;
6746     }
6747     // give caller the default choice even if we will not make it
6748     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6749     partner = piece; // pieces can promote if the pieceToCharTable says so
6750     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6751     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6752     if(        sweepSelect && gameInfo.variant != VariantGreat
6753                            && gameInfo.variant != VariantGrand
6754                            && gameInfo.variant != VariantSuper) return FALSE;
6755     if(autoQueen) return FALSE; // predetermined
6756
6757     // suppress promotion popup on illegal moves that are not premoves
6758     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6759               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6760     if(appData.testLegality && !premove) {
6761         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6762                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6763         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6764         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6765             return FALSE;
6766     }
6767
6768     return TRUE;
6769 }
6770
6771 int
6772 InPalace (int row, int column)
6773 {   /* [HGM] for Xiangqi */
6774     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6775          column < (BOARD_WIDTH + 4)/2 &&
6776          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6777     return FALSE;
6778 }
6779
6780 int
6781 PieceForSquare (int x, int y)
6782 {
6783   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6784      return -1;
6785   else
6786      return boards[currentMove][y][x];
6787 }
6788
6789 int
6790 OKToStartUserMove (int x, int y)
6791 {
6792     ChessSquare from_piece;
6793     int white_piece;
6794
6795     if (matchMode) return FALSE;
6796     if (gameMode == EditPosition) return TRUE;
6797
6798     if (x >= 0 && y >= 0)
6799       from_piece = boards[currentMove][y][x];
6800     else
6801       from_piece = EmptySquare;
6802
6803     if (from_piece == EmptySquare) return FALSE;
6804
6805     white_piece = (int)from_piece >= (int)WhitePawn &&
6806       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6807
6808     switch (gameMode) {
6809       case AnalyzeFile:
6810       case TwoMachinesPlay:
6811       case EndOfGame:
6812         return FALSE;
6813
6814       case IcsObserving:
6815       case IcsIdle:
6816         return FALSE;
6817
6818       case MachinePlaysWhite:
6819       case IcsPlayingBlack:
6820         if (appData.zippyPlay) return FALSE;
6821         if (white_piece) {
6822             DisplayMoveError(_("You are playing Black"));
6823             return FALSE;
6824         }
6825         break;
6826
6827       case MachinePlaysBlack:
6828       case IcsPlayingWhite:
6829         if (appData.zippyPlay) return FALSE;
6830         if (!white_piece) {
6831             DisplayMoveError(_("You are playing White"));
6832             return FALSE;
6833         }
6834         break;
6835
6836       case PlayFromGameFile:
6837             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6838       case EditGame:
6839         if (!white_piece && WhiteOnMove(currentMove)) {
6840             DisplayMoveError(_("It is White's turn"));
6841             return FALSE;
6842         }
6843         if (white_piece && !WhiteOnMove(currentMove)) {
6844             DisplayMoveError(_("It is Black's turn"));
6845             return FALSE;
6846         }
6847         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6848             /* Editing correspondence game history */
6849             /* Could disallow this or prompt for confirmation */
6850             cmailOldMove = -1;
6851         }
6852         break;
6853
6854       case BeginningOfGame:
6855         if (appData.icsActive) return FALSE;
6856         if (!appData.noChessProgram) {
6857             if (!white_piece) {
6858                 DisplayMoveError(_("You are playing White"));
6859                 return FALSE;
6860             }
6861         }
6862         break;
6863
6864       case Training:
6865         if (!white_piece && WhiteOnMove(currentMove)) {
6866             DisplayMoveError(_("It is White's turn"));
6867             return FALSE;
6868         }
6869         if (white_piece && !WhiteOnMove(currentMove)) {
6870             DisplayMoveError(_("It is Black's turn"));
6871             return FALSE;
6872         }
6873         break;
6874
6875       default:
6876       case IcsExamining:
6877         break;
6878     }
6879     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6880         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6881         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6882         && gameMode != AnalyzeFile && gameMode != Training) {
6883         DisplayMoveError(_("Displayed position is not current"));
6884         return FALSE;
6885     }
6886     return TRUE;
6887 }
6888
6889 Boolean
6890 OnlyMove (int *x, int *y, Boolean captures)
6891 {
6892     DisambiguateClosure cl;
6893     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6894     switch(gameMode) {
6895       case MachinePlaysBlack:
6896       case IcsPlayingWhite:
6897       case BeginningOfGame:
6898         if(!WhiteOnMove(currentMove)) return FALSE;
6899         break;
6900       case MachinePlaysWhite:
6901       case IcsPlayingBlack:
6902         if(WhiteOnMove(currentMove)) return FALSE;
6903         break;
6904       case EditGame:
6905         break;
6906       default:
6907         return FALSE;
6908     }
6909     cl.pieceIn = EmptySquare;
6910     cl.rfIn = *y;
6911     cl.ffIn = *x;
6912     cl.rtIn = -1;
6913     cl.ftIn = -1;
6914     cl.promoCharIn = NULLCHAR;
6915     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6916     if( cl.kind == NormalMove ||
6917         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6918         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6919         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6920       fromX = cl.ff;
6921       fromY = cl.rf;
6922       *x = cl.ft;
6923       *y = cl.rt;
6924       return TRUE;
6925     }
6926     if(cl.kind != ImpossibleMove) return FALSE;
6927     cl.pieceIn = EmptySquare;
6928     cl.rfIn = -1;
6929     cl.ffIn = -1;
6930     cl.rtIn = *y;
6931     cl.ftIn = *x;
6932     cl.promoCharIn = NULLCHAR;
6933     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6934     if( cl.kind == NormalMove ||
6935         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6936         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6937         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6938       fromX = cl.ff;
6939       fromY = cl.rf;
6940       *x = cl.ft;
6941       *y = cl.rt;
6942       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6943       return TRUE;
6944     }
6945     return FALSE;
6946 }
6947
6948 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6949 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6950 int lastLoadGameUseList = FALSE;
6951 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6952 ChessMove lastLoadGameStart = EndOfFile;
6953 int doubleClick;
6954 Boolean addToBookFlag;
6955
6956 void
6957 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6958 {
6959     ChessMove moveType;
6960     ChessSquare pup;
6961     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6962
6963     /* Check if the user is playing in turn.  This is complicated because we
6964        let the user "pick up" a piece before it is his turn.  So the piece he
6965        tried to pick up may have been captured by the time he puts it down!
6966        Therefore we use the color the user is supposed to be playing in this
6967        test, not the color of the piece that is currently on the starting
6968        square---except in EditGame mode, where the user is playing both
6969        sides; fortunately there the capture race can't happen.  (It can
6970        now happen in IcsExamining mode, but that's just too bad.  The user
6971        will get a somewhat confusing message in that case.)
6972        */
6973
6974     switch (gameMode) {
6975       case AnalyzeFile:
6976       case TwoMachinesPlay:
6977       case EndOfGame:
6978       case IcsObserving:
6979       case IcsIdle:
6980         /* We switched into a game mode where moves are not accepted,
6981            perhaps while the mouse button was down. */
6982         return;
6983
6984       case MachinePlaysWhite:
6985         /* User is moving for Black */
6986         if (WhiteOnMove(currentMove)) {
6987             DisplayMoveError(_("It is White's turn"));
6988             return;
6989         }
6990         break;
6991
6992       case MachinePlaysBlack:
6993         /* User is moving for White */
6994         if (!WhiteOnMove(currentMove)) {
6995             DisplayMoveError(_("It is Black's turn"));
6996             return;
6997         }
6998         break;
6999
7000       case PlayFromGameFile:
7001             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7002       case EditGame:
7003       case IcsExamining:
7004       case BeginningOfGame:
7005       case AnalyzeMode:
7006       case Training:
7007         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7008         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7009             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7010             /* User is moving for Black */
7011             if (WhiteOnMove(currentMove)) {
7012                 DisplayMoveError(_("It is White's turn"));
7013                 return;
7014             }
7015         } else {
7016             /* User is moving for White */
7017             if (!WhiteOnMove(currentMove)) {
7018                 DisplayMoveError(_("It is Black's turn"));
7019                 return;
7020             }
7021         }
7022         break;
7023
7024       case IcsPlayingBlack:
7025         /* User is moving for Black */
7026         if (WhiteOnMove(currentMove)) {
7027             if (!appData.premove) {
7028                 DisplayMoveError(_("It is White's turn"));
7029             } else if (toX >= 0 && toY >= 0) {
7030                 premoveToX = toX;
7031                 premoveToY = toY;
7032                 premoveFromX = fromX;
7033                 premoveFromY = fromY;
7034                 premovePromoChar = promoChar;
7035                 gotPremove = 1;
7036                 if (appData.debugMode)
7037                     fprintf(debugFP, "Got premove: fromX %d,"
7038                             "fromY %d, toX %d, toY %d\n",
7039                             fromX, fromY, toX, toY);
7040             }
7041             return;
7042         }
7043         break;
7044
7045       case IcsPlayingWhite:
7046         /* User is moving for White */
7047         if (!WhiteOnMove(currentMove)) {
7048             if (!appData.premove) {
7049                 DisplayMoveError(_("It is Black's turn"));
7050             } else if (toX >= 0 && toY >= 0) {
7051                 premoveToX = toX;
7052                 premoveToY = toY;
7053                 premoveFromX = fromX;
7054                 premoveFromY = fromY;
7055                 premovePromoChar = promoChar;
7056                 gotPremove = 1;
7057                 if (appData.debugMode)
7058                     fprintf(debugFP, "Got premove: fromX %d,"
7059                             "fromY %d, toX %d, toY %d\n",
7060                             fromX, fromY, toX, toY);
7061             }
7062             return;
7063         }
7064         break;
7065
7066       default:
7067         break;
7068
7069       case EditPosition:
7070         /* EditPosition, empty square, or different color piece;
7071            click-click move is possible */
7072         if (toX == -2 || toY == -2) {
7073             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7074             DrawPosition(FALSE, boards[currentMove]);
7075             return;
7076         } else if (toX >= 0 && toY >= 0) {
7077             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7078                 ChessSquare q, p = boards[0][rf][ff];
7079                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
7080                 if(CHUPROMOTED(p) < BlackPawn) p = q = CHUPROMOTED(boards[0][rf][ff]);
7081                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
7082                 if(PieceToChar(q) == '+') gatingPiece = p;
7083             }
7084             boards[0][toY][toX] = boards[0][fromY][fromX];
7085             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7086                 if(boards[0][fromY][0] != EmptySquare) {
7087                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7088                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7089                 }
7090             } else
7091             if(fromX == BOARD_RGHT+1) {
7092                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7093                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7094                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7095                 }
7096             } else
7097             boards[0][fromY][fromX] = gatingPiece;
7098             DrawPosition(FALSE, boards[currentMove]);
7099             return;
7100         }
7101         return;
7102     }
7103
7104     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7105     pup = boards[currentMove][toY][toX];
7106
7107     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7108     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7109          if( pup != EmptySquare ) return;
7110          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7111            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7112                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7113            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7114            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7115            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7116            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7117          fromY = DROP_RANK;
7118     }
7119
7120     /* [HGM] always test for legality, to get promotion info */
7121     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7122                                          fromY, fromX, toY, toX, promoChar);
7123
7124     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7125
7126     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7127
7128     /* [HGM] but possibly ignore an IllegalMove result */
7129     if (appData.testLegality) {
7130         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7131             DisplayMoveError(_("Illegal move"));
7132             return;
7133         }
7134     }
7135
7136     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7137         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7138              ClearPremoveHighlights(); // was included
7139         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7140         return;
7141     }
7142
7143     if(addToBookFlag) { // adding moves to book
7144         char buf[MSG_SIZ], move[MSG_SIZ];
7145         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7146         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');
7147         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7148         AddBookMove(buf);
7149         addToBookFlag = FALSE;
7150         ClearHighlights();
7151         return;
7152     }
7153
7154     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7155 }
7156
7157 /* Common tail of UserMoveEvent and DropMenuEvent */
7158 int
7159 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7160 {
7161     char *bookHit = 0;
7162
7163     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7164         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7165         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7166         if(WhiteOnMove(currentMove)) {
7167             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7168         } else {
7169             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7170         }
7171     }
7172
7173     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7174        move type in caller when we know the move is a legal promotion */
7175     if(moveType == NormalMove && promoChar)
7176         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7177
7178     /* [HGM] <popupFix> The following if has been moved here from
7179        UserMoveEvent(). Because it seemed to belong here (why not allow
7180        piece drops in training games?), and because it can only be
7181        performed after it is known to what we promote. */
7182     if (gameMode == Training) {
7183       /* compare the move played on the board to the next move in the
7184        * game. If they match, display the move and the opponent's response.
7185        * If they don't match, display an error message.
7186        */
7187       int saveAnimate;
7188       Board testBoard;
7189       CopyBoard(testBoard, boards[currentMove]);
7190       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7191
7192       if (CompareBoards(testBoard, boards[currentMove+1])) {
7193         ForwardInner(currentMove+1);
7194
7195         /* Autoplay the opponent's response.
7196          * if appData.animate was TRUE when Training mode was entered,
7197          * the response will be animated.
7198          */
7199         saveAnimate = appData.animate;
7200         appData.animate = animateTraining;
7201         ForwardInner(currentMove+1);
7202         appData.animate = saveAnimate;
7203
7204         /* check for the end of the game */
7205         if (currentMove >= forwardMostMove) {
7206           gameMode = PlayFromGameFile;
7207           ModeHighlight();
7208           SetTrainingModeOff();
7209           DisplayInformation(_("End of game"));
7210         }
7211       } else {
7212         DisplayError(_("Incorrect move"), 0);
7213       }
7214       return 1;
7215     }
7216
7217   /* Ok, now we know that the move is good, so we can kill
7218      the previous line in Analysis Mode */
7219   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7220                                 && currentMove < forwardMostMove) {
7221     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7222     else forwardMostMove = currentMove;
7223   }
7224
7225   ClearMap();
7226
7227   /* If we need the chess program but it's dead, restart it */
7228   ResurrectChessProgram();
7229
7230   /* A user move restarts a paused game*/
7231   if (pausing)
7232     PauseEvent();
7233
7234   thinkOutput[0] = NULLCHAR;
7235
7236   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7237
7238   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7239     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7240     return 1;
7241   }
7242
7243   if (gameMode == BeginningOfGame) {
7244     if (appData.noChessProgram) {
7245       gameMode = EditGame;
7246       SetGameInfo();
7247     } else {
7248       char buf[MSG_SIZ];
7249       gameMode = MachinePlaysBlack;
7250       StartClocks();
7251       SetGameInfo();
7252       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7253       DisplayTitle(buf);
7254       if (first.sendName) {
7255         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7256         SendToProgram(buf, &first);
7257       }
7258       StartClocks();
7259     }
7260     ModeHighlight();
7261   }
7262
7263   /* Relay move to ICS or chess engine */
7264   if (appData.icsActive) {
7265     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7266         gameMode == IcsExamining) {
7267       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7268         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7269         SendToICS("draw ");
7270         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7271       }
7272       // also send plain move, in case ICS does not understand atomic claims
7273       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7274       ics_user_moved = 1;
7275     }
7276   } else {
7277     if (first.sendTime && (gameMode == BeginningOfGame ||
7278                            gameMode == MachinePlaysWhite ||
7279                            gameMode == MachinePlaysBlack)) {
7280       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7281     }
7282     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7283          // [HGM] book: if program might be playing, let it use book
7284         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7285         first.maybeThinking = TRUE;
7286     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7287         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7288         SendBoard(&first, currentMove+1);
7289         if(second.analyzing) {
7290             if(!second.useSetboard) SendToProgram("undo\n", &second);
7291             SendBoard(&second, currentMove+1);
7292         }
7293     } else {
7294         SendMoveToProgram(forwardMostMove-1, &first);
7295         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7296     }
7297     if (currentMove == cmailOldMove + 1) {
7298       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7299     }
7300   }
7301
7302   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7303
7304   switch (gameMode) {
7305   case EditGame:
7306     if(appData.testLegality)
7307     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7308     case MT_NONE:
7309     case MT_CHECK:
7310       break;
7311     case MT_CHECKMATE:
7312     case MT_STAINMATE:
7313       if (WhiteOnMove(currentMove)) {
7314         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7315       } else {
7316         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7317       }
7318       break;
7319     case MT_STALEMATE:
7320       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7321       break;
7322     }
7323     break;
7324
7325   case MachinePlaysBlack:
7326   case MachinePlaysWhite:
7327     /* disable certain menu options while machine is thinking */
7328     SetMachineThinkingEnables();
7329     break;
7330
7331   default:
7332     break;
7333   }
7334
7335   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7336   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7337
7338   if(bookHit) { // [HGM] book: simulate book reply
7339         static char bookMove[MSG_SIZ]; // a bit generous?
7340
7341         programStats.nodes = programStats.depth = programStats.time =
7342         programStats.score = programStats.got_only_move = 0;
7343         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7344
7345         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7346         strcat(bookMove, bookHit);
7347         HandleMachineMove(bookMove, &first);
7348   }
7349   return 1;
7350 }
7351
7352 void
7353 MarkByFEN(char *fen)
7354 {
7355         int r, f;
7356         if(!appData.markers || !appData.highlightDragging) return;
7357         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7358         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7359         while(*fen) {
7360             int s = 0;
7361             marker[r][f] = 0;
7362             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7363             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7364             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7365             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7366             if(*fen == 'T') marker[r][f++] = 0; else
7367             if(*fen == 'Y') marker[r][f++] = 1; else
7368             if(*fen == 'G') marker[r][f++] = 3; else
7369             if(*fen == 'B') marker[r][f++] = 4; else
7370             if(*fen == 'C') marker[r][f++] = 5; else
7371             if(*fen == 'M') marker[r][f++] = 6; else
7372             if(*fen == 'W') marker[r][f++] = 7; else
7373             if(*fen == 'D') marker[r][f++] = 8; else
7374             if(*fen == 'R') marker[r][f++] = 2; else {
7375                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7376               f += s; fen -= s>0;
7377             }
7378             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7379             if(r < 0) break;
7380             fen++;
7381         }
7382         DrawPosition(TRUE, NULL);
7383 }
7384
7385 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7386
7387 void
7388 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7389 {
7390     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7391     Markers *m = (Markers *) closure;
7392     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7393         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7394                          || kind == WhiteCapturesEnPassant
7395                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 3;
7396     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7397 }
7398
7399 static int hoverSavedValid;
7400
7401 void
7402 MarkTargetSquares (int clear)
7403 {
7404   int x, y, sum=0;
7405   if(clear) { // no reason to ever suppress clearing
7406     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7407     hoverSavedValid = 0;
7408     if(!sum) return; // nothing was cleared,no redraw needed
7409   } else {
7410     int capt = 0;
7411     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7412        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7413     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7414     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7415       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7416       if(capt)
7417       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7418     }
7419   }
7420   DrawPosition(FALSE, NULL);
7421 }
7422
7423 int
7424 Explode (Board board, int fromX, int fromY, int toX, int toY)
7425 {
7426     if(gameInfo.variant == VariantAtomic &&
7427        (board[toY][toX] != EmptySquare ||                     // capture?
7428         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7429                          board[fromY][fromX] == BlackPawn   )
7430       )) {
7431         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7432         return TRUE;
7433     }
7434     return FALSE;
7435 }
7436
7437 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7438
7439 int
7440 CanPromote (ChessSquare piece, int y)
7441 {
7442         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7443         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7444         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7445         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7446            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7447            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7448          gameInfo.variant == VariantMakruk) return FALSE;
7449         return (piece == BlackPawn && y <= zone ||
7450                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7451                 piece == BlackLance && y <= zone ||
7452                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7453 }
7454
7455 void
7456 HoverEvent (int xPix, int yPix, int x, int y)
7457 {
7458         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7459         int r, f;
7460         if(!first.highlight) return;
7461         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7462         if(x == oldX && y == oldY) return; // only do something if we enter new square
7463         oldFromX = fromX; oldFromY = fromY;
7464         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7465           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7466             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7467           hoverSavedValid = 1;
7468         } else if(oldX != x || oldY != y) {
7469           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7470           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7471           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7472             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7473           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7474             char buf[MSG_SIZ];
7475             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7476             SendToProgram(buf, &first);
7477           }
7478           oldX = x; oldY = y;
7479 //        SetHighlights(fromX, fromY, x, y);
7480         }
7481 }
7482
7483 void ReportClick(char *action, int x, int y)
7484 {
7485         char buf[MSG_SIZ]; // Inform engine of what user does
7486         int r, f;
7487         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7488           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7489             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7490         if(!first.highlight || gameMode == EditPosition) return;
7491         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7492         SendToProgram(buf, &first);
7493 }
7494
7495 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7496
7497 void
7498 LeftClick (ClickType clickType, int xPix, int yPix)
7499 {
7500     int x, y;
7501     Boolean saveAnimate;
7502     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7503     char promoChoice = NULLCHAR;
7504     ChessSquare piece;
7505     static TimeMark lastClickTime, prevClickTime;
7506
7507     x = EventToSquare(xPix, BOARD_WIDTH);
7508     y = EventToSquare(yPix, BOARD_HEIGHT);
7509     if (!flipView && y >= 0) {
7510         y = BOARD_HEIGHT - 1 - y;
7511     }
7512     if (flipView && x >= 0) {
7513         x = BOARD_WIDTH - 1 - x;
7514     }
7515
7516     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7517         static int dummy;
7518         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7519         right = TRUE;
7520         return;
7521     }
7522
7523     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7524
7525     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7526
7527     if (clickType == Press) ErrorPopDown();
7528     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7529
7530     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7531         defaultPromoChoice = promoSweep;
7532         promoSweep = EmptySquare;   // terminate sweep
7533         promoDefaultAltered = TRUE;
7534         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7535     }
7536
7537     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7538         if(clickType == Release) return; // ignore upclick of click-click destination
7539         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7540         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7541         if(gameInfo.holdingsWidth &&
7542                 (WhiteOnMove(currentMove)
7543                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7544                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7545             // click in right holdings, for determining promotion piece
7546             ChessSquare p = boards[currentMove][y][x];
7547             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7548             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7549             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7550                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7551                 fromX = fromY = -1;
7552                 return;
7553             }
7554         }
7555         DrawPosition(FALSE, boards[currentMove]);
7556         return;
7557     }
7558
7559     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7560     if(clickType == Press
7561             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7562               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7563               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7564         return;
7565
7566     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7567         // could be static click on premove from-square: abort premove
7568         gotPremove = 0;
7569         ClearPremoveHighlights();
7570     }
7571
7572     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7573         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7574
7575     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7576         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7577                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7578         defaultPromoChoice = DefaultPromoChoice(side);
7579     }
7580
7581     autoQueen = appData.alwaysPromoteToQueen;
7582
7583     if (fromX == -1) {
7584       int originalY = y;
7585       gatingPiece = EmptySquare;
7586       if (clickType != Press) {
7587         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7588             DragPieceEnd(xPix, yPix); dragging = 0;
7589             DrawPosition(FALSE, NULL);
7590         }
7591         return;
7592       }
7593       doubleClick = FALSE;
7594       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7595         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7596       }
7597       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7598       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7599          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7600          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7601             /* First square */
7602             if (OKToStartUserMove(fromX, fromY)) {
7603                 second = 0;
7604                 ReportClick("lift", x, y);
7605                 MarkTargetSquares(0);
7606                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7607                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7608                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7609                     promoSweep = defaultPromoChoice;
7610                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7611                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7612                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7613                 }
7614                 if (appData.highlightDragging) {
7615                     SetHighlights(fromX, fromY, -1, -1);
7616                 } else {
7617                     ClearHighlights();
7618                 }
7619             } else fromX = fromY = -1;
7620             return;
7621         }
7622     }
7623
7624     /* fromX != -1 */
7625     if (clickType == Press && gameMode != EditPosition) {
7626         ChessSquare fromP;
7627         ChessSquare toP;
7628         int frc;
7629
7630         // ignore off-board to clicks
7631         if(y < 0 || x < 0) return;
7632
7633         /* Check if clicking again on the same color piece */
7634         fromP = boards[currentMove][fromY][fromX];
7635         toP = boards[currentMove][y][x];
7636         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7637         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7638             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7639            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7640              WhitePawn <= toP && toP <= WhiteKing &&
7641              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7642              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7643             (BlackPawn <= fromP && fromP <= BlackKing &&
7644              BlackPawn <= toP && toP <= BlackKing &&
7645              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7646              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7647             /* Clicked again on same color piece -- changed his mind */
7648             second = (x == fromX && y == fromY);
7649             killX = killY = -1;
7650             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7651                 second = FALSE; // first double-click rather than scond click
7652                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7653             }
7654             promoDefaultAltered = FALSE;
7655             MarkTargetSquares(1);
7656            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7657             if (appData.highlightDragging) {
7658                 SetHighlights(x, y, -1, -1);
7659             } else {
7660                 ClearHighlights();
7661             }
7662             if (OKToStartUserMove(x, y)) {
7663                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7664                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7665                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7666                  gatingPiece = boards[currentMove][fromY][fromX];
7667                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7668                 fromX = x;
7669                 fromY = y; dragging = 1;
7670                 if(!second) ReportClick("lift", x, y);
7671                 MarkTargetSquares(0);
7672                 DragPieceBegin(xPix, yPix, FALSE);
7673                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7674                     promoSweep = defaultPromoChoice;
7675                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7676                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7677                 }
7678             }
7679            }
7680            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7681            second = FALSE;
7682         }
7683         // ignore clicks on holdings
7684         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7685     }
7686
7687     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7688         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7689         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7690         return;
7691     }
7692
7693     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7694         DragPieceEnd(xPix, yPix); dragging = 0;
7695         if(clearFlag) {
7696             // a deferred attempt to click-click move an empty square on top of a piece
7697             boards[currentMove][y][x] = EmptySquare;
7698             ClearHighlights();
7699             DrawPosition(FALSE, boards[currentMove]);
7700             fromX = fromY = -1; clearFlag = 0;
7701             return;
7702         }
7703         if (appData.animateDragging) {
7704             /* Undo animation damage if any */
7705             DrawPosition(FALSE, NULL);
7706         }
7707         if (second) {
7708             /* Second up/down in same square; just abort move */
7709             second = 0;
7710             fromX = fromY = -1;
7711             gatingPiece = EmptySquare;
7712             MarkTargetSquares(1);
7713             ClearHighlights();
7714             gotPremove = 0;
7715             ClearPremoveHighlights();
7716         } else {
7717             /* First upclick in same square; start click-click mode */
7718             SetHighlights(x, y, -1, -1);
7719         }
7720         return;
7721     }
7722
7723     clearFlag = 0;
7724
7725     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7726        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7727         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7728         DisplayMessage(_("only marked squares are legal"),"");
7729         DrawPosition(TRUE, NULL);
7730         return; // ignore to-click
7731     }
7732
7733     /* we now have a different from- and (possibly off-board) to-square */
7734     /* Completed move */
7735     if(!sweepSelecting) {
7736         toX = x;
7737         toY = y;
7738     }
7739
7740     piece = boards[currentMove][fromY][fromX];
7741
7742     saveAnimate = appData.animate;
7743     if (clickType == Press) {
7744         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7745         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7746             // must be Edit Position mode with empty-square selected
7747             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7748             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7749             return;
7750         }
7751         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7752             return;
7753         }
7754         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7755             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7756         } else
7757         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7758         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7759           if(appData.sweepSelect) {
7760             promoSweep = defaultPromoChoice;
7761             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7762             selectFlag = 0; lastX = xPix; lastY = yPix;
7763             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7764             Sweep(0); // Pawn that is going to promote: preview promotion piece
7765             sweepSelecting = 1;
7766             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7767             MarkTargetSquares(1);
7768           }
7769           return; // promo popup appears on up-click
7770         }
7771         /* Finish clickclick move */
7772         if (appData.animate || appData.highlightLastMove) {
7773             SetHighlights(fromX, fromY, toX, toY);
7774         } else {
7775             ClearHighlights();
7776         }
7777     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7778         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7779         *promoRestrict = 0;
7780         if (appData.animate || appData.highlightLastMove) {
7781             SetHighlights(fromX, fromY, toX, toY);
7782         } else {
7783             ClearHighlights();
7784         }
7785     } else {
7786 #if 0
7787 // [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
7788         /* Finish drag move */
7789         if (appData.highlightLastMove) {
7790             SetHighlights(fromX, fromY, toX, toY);
7791         } else {
7792             ClearHighlights();
7793         }
7794 #endif
7795         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7796           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7797         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7798         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7799           dragging *= 2;            // flag button-less dragging if we are dragging
7800           MarkTargetSquares(1);
7801           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7802           else {
7803             kill2X = killX; kill2Y = killY;
7804             killX = x; killY = y;     //remeber this square as intermediate
7805             ReportClick("put", x, y); // and inform engine
7806             ReportClick("lift", x, y);
7807             MarkTargetSquares(0);
7808             return;
7809           }
7810         }
7811         DragPieceEnd(xPix, yPix); dragging = 0;
7812         /* Don't animate move and drag both */
7813         appData.animate = FALSE;
7814     }
7815
7816     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7817     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7818         ChessSquare piece = boards[currentMove][fromY][fromX];
7819         if(gameMode == EditPosition && piece != EmptySquare &&
7820            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7821             int n;
7822
7823             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7824                 n = PieceToNumber(piece - (int)BlackPawn);
7825                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7826                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7827                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7828             } else
7829             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7830                 n = PieceToNumber(piece);
7831                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7832                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7833                 boards[currentMove][n][BOARD_WIDTH-2]++;
7834             }
7835             boards[currentMove][fromY][fromX] = EmptySquare;
7836         }
7837         ClearHighlights();
7838         fromX = fromY = -1;
7839         MarkTargetSquares(1);
7840         DrawPosition(TRUE, boards[currentMove]);
7841         return;
7842     }
7843
7844     // off-board moves should not be highlighted
7845     if(x < 0 || y < 0) ClearHighlights();
7846     else ReportClick("put", x, y);
7847
7848     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7849
7850     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7851
7852     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7853         SetHighlights(fromX, fromY, toX, toY);
7854         MarkTargetSquares(1);
7855         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7856             // [HGM] super: promotion to captured piece selected from holdings
7857             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7858             promotionChoice = TRUE;
7859             // kludge follows to temporarily execute move on display, without promoting yet
7860             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7861             boards[currentMove][toY][toX] = p;
7862             DrawPosition(FALSE, boards[currentMove]);
7863             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7864             boards[currentMove][toY][toX] = q;
7865             DisplayMessage("Click in holdings to choose piece", "");
7866             return;
7867         }
7868         PromotionPopUp(promoChoice);
7869     } else {
7870         int oldMove = currentMove;
7871         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7872         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7873         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7874         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7875            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7876             DrawPosition(TRUE, boards[currentMove]);
7877         MarkTargetSquares(1);
7878         fromX = fromY = -1;
7879     }
7880     appData.animate = saveAnimate;
7881     if (appData.animate || appData.animateDragging) {
7882         /* Undo animation damage if needed */
7883         DrawPosition(FALSE, NULL);
7884     }
7885 }
7886
7887 int
7888 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7889 {   // front-end-free part taken out of PieceMenuPopup
7890     int whichMenu; int xSqr, ySqr;
7891
7892     if(seekGraphUp) { // [HGM] seekgraph
7893         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7894         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7895         return -2;
7896     }
7897
7898     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7899          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7900         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7901         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7902         if(action == Press)   {
7903             originalFlip = flipView;
7904             flipView = !flipView; // temporarily flip board to see game from partners perspective
7905             DrawPosition(TRUE, partnerBoard);
7906             DisplayMessage(partnerStatus, "");
7907             partnerUp = TRUE;
7908         } else if(action == Release) {
7909             flipView = originalFlip;
7910             DrawPosition(TRUE, boards[currentMove]);
7911             partnerUp = FALSE;
7912         }
7913         return -2;
7914     }
7915
7916     xSqr = EventToSquare(x, BOARD_WIDTH);
7917     ySqr = EventToSquare(y, BOARD_HEIGHT);
7918     if (action == Release) {
7919         if(pieceSweep != EmptySquare) {
7920             EditPositionMenuEvent(pieceSweep, toX, toY);
7921             pieceSweep = EmptySquare;
7922         } else UnLoadPV(); // [HGM] pv
7923     }
7924     if (action != Press) return -2; // return code to be ignored
7925     switch (gameMode) {
7926       case IcsExamining:
7927         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7928       case EditPosition:
7929         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7930         if (xSqr < 0 || ySqr < 0) return -1;
7931         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7932         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7933         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7934         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7935         NextPiece(0);
7936         return 2; // grab
7937       case IcsObserving:
7938         if(!appData.icsEngineAnalyze) return -1;
7939       case IcsPlayingWhite:
7940       case IcsPlayingBlack:
7941         if(!appData.zippyPlay) goto noZip;
7942       case AnalyzeMode:
7943       case AnalyzeFile:
7944       case MachinePlaysWhite:
7945       case MachinePlaysBlack:
7946       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7947         if (!appData.dropMenu) {
7948           LoadPV(x, y);
7949           return 2; // flag front-end to grab mouse events
7950         }
7951         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7952            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7953       case EditGame:
7954       noZip:
7955         if (xSqr < 0 || ySqr < 0) return -1;
7956         if (!appData.dropMenu || appData.testLegality &&
7957             gameInfo.variant != VariantBughouse &&
7958             gameInfo.variant != VariantCrazyhouse) return -1;
7959         whichMenu = 1; // drop menu
7960         break;
7961       default:
7962         return -1;
7963     }
7964
7965     if (((*fromX = xSqr) < 0) ||
7966         ((*fromY = ySqr) < 0)) {
7967         *fromX = *fromY = -1;
7968         return -1;
7969     }
7970     if (flipView)
7971       *fromX = BOARD_WIDTH - 1 - *fromX;
7972     else
7973       *fromY = BOARD_HEIGHT - 1 - *fromY;
7974
7975     return whichMenu;
7976 }
7977
7978 void
7979 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7980 {
7981 //    char * hint = lastHint;
7982     FrontEndProgramStats stats;
7983
7984     stats.which = cps == &first ? 0 : 1;
7985     stats.depth = cpstats->depth;
7986     stats.nodes = cpstats->nodes;
7987     stats.score = cpstats->score;
7988     stats.time = cpstats->time;
7989     stats.pv = cpstats->movelist;
7990     stats.hint = lastHint;
7991     stats.an_move_index = 0;
7992     stats.an_move_count = 0;
7993
7994     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7995         stats.hint = cpstats->move_name;
7996         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7997         stats.an_move_count = cpstats->nr_moves;
7998     }
7999
8000     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
8001
8002     SetProgramStats( &stats );
8003 }
8004
8005 void
8006 ClearEngineOutputPane (int which)
8007 {
8008     static FrontEndProgramStats dummyStats;
8009     dummyStats.which = which;
8010     dummyStats.pv = "#";
8011     SetProgramStats( &dummyStats );
8012 }
8013
8014 #define MAXPLAYERS 500
8015
8016 char *
8017 TourneyStandings (int display)
8018 {
8019     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8020     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8021     char result, *p, *names[MAXPLAYERS];
8022
8023     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8024         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8025     names[0] = p = strdup(appData.participants);
8026     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8027
8028     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8029
8030     while(result = appData.results[nr]) {
8031         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8032         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8033         wScore = bScore = 0;
8034         switch(result) {
8035           case '+': wScore = 2; break;
8036           case '-': bScore = 2; break;
8037           case '=': wScore = bScore = 1; break;
8038           case ' ':
8039           case '*': return strdup("busy"); // tourney not finished
8040         }
8041         score[w] += wScore;
8042         score[b] += bScore;
8043         games[w]++;
8044         games[b]++;
8045         nr++;
8046     }
8047     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8048     for(w=0; w<nPlayers; w++) {
8049         bScore = -1;
8050         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8051         ranking[w] = b; points[w] = bScore; score[b] = -2;
8052     }
8053     p = malloc(nPlayers*34+1);
8054     for(w=0; w<nPlayers && w<display; w++)
8055         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8056     free(names[0]);
8057     return p;
8058 }
8059
8060 void
8061 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8062 {       // count all piece types
8063         int p, f, r;
8064         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8065         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8066         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8067                 p = board[r][f];
8068                 pCnt[p]++;
8069                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8070                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8071                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8072                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8073                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8074                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8075         }
8076 }
8077
8078 int
8079 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8080 {
8081         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8082         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8083
8084         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8085         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8086         if(myPawns == 2 && nMine == 3) // KPP
8087             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8088         if(myPawns == 1 && nMine == 2) // KP
8089             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8090         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8091             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8092         if(myPawns) return FALSE;
8093         if(pCnt[WhiteRook+side])
8094             return pCnt[BlackRook-side] ||
8095                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8096                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8097                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8098         if(pCnt[WhiteCannon+side]) {
8099             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8100             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8101         }
8102         if(pCnt[WhiteKnight+side])
8103             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8104         return FALSE;
8105 }
8106
8107 int
8108 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8109 {
8110         VariantClass v = gameInfo.variant;
8111
8112         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8113         if(v == VariantShatranj) return TRUE; // always winnable through baring
8114         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8115         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8116
8117         if(v == VariantXiangqi) {
8118                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8119
8120                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8121                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8122                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8123                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8124                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8125                 if(stale) // we have at least one last-rank P plus perhaps C
8126                     return majors // KPKX
8127                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8128                 else // KCA*E*
8129                     return pCnt[WhiteFerz+side] // KCAK
8130                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8131                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8132                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8133
8134         } else if(v == VariantKnightmate) {
8135                 if(nMine == 1) return FALSE;
8136                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8137         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8138                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8139
8140                 if(nMine == 1) return FALSE; // bare King
8141                 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
8142                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8143                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8144                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8145                 if(pCnt[WhiteKnight+side])
8146                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8147                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8148                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8149                 if(nBishops)
8150                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8151                 if(pCnt[WhiteAlfil+side])
8152                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8153                 if(pCnt[WhiteWazir+side])
8154                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8155         }
8156
8157         return TRUE;
8158 }
8159
8160 int
8161 CompareWithRights (Board b1, Board b2)
8162 {
8163     int rights = 0;
8164     if(!CompareBoards(b1, b2)) return FALSE;
8165     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8166     /* compare castling rights */
8167     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8168            rights++; /* King lost rights, while rook still had them */
8169     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8170         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8171            rights++; /* but at least one rook lost them */
8172     }
8173     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8174            rights++;
8175     if( b1[CASTLING][5] != NoRights ) {
8176         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8177            rights++;
8178     }
8179     return rights == 0;
8180 }
8181
8182 int
8183 Adjudicate (ChessProgramState *cps)
8184 {       // [HGM] some adjudications useful with buggy engines
8185         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8186         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8187         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8188         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8189         int k, drop, count = 0; static int bare = 1;
8190         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8191         Boolean canAdjudicate = !appData.icsActive;
8192
8193         // most tests only when we understand the game, i.e. legality-checking on
8194             if( appData.testLegality )
8195             {   /* [HGM] Some more adjudications for obstinate engines */
8196                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8197                 static int moveCount = 6;
8198                 ChessMove result;
8199                 char *reason = NULL;
8200
8201                 /* Count what is on board. */
8202                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8203
8204                 /* Some material-based adjudications that have to be made before stalemate test */
8205                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8206                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8207                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8208                      if(canAdjudicate && appData.checkMates) {
8209                          if(engineOpponent)
8210                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8211                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8212                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8213                          return 1;
8214                      }
8215                 }
8216
8217                 /* Bare King in Shatranj (loses) or Losers (wins) */
8218                 if( nrW == 1 || nrB == 1) {
8219                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8220                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8221                      if(canAdjudicate && appData.checkMates) {
8222                          if(engineOpponent)
8223                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8224                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8225                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8226                          return 1;
8227                      }
8228                   } else
8229                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8230                   {    /* bare King */
8231                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8232                         if(canAdjudicate && appData.checkMates) {
8233                             /* but only adjudicate if adjudication enabled */
8234                             if(engineOpponent)
8235                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8236                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8237                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8238                             return 1;
8239                         }
8240                   }
8241                 } else bare = 1;
8242
8243
8244             // don't wait for engine to announce game end if we can judge ourselves
8245             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8246               case MT_CHECK:
8247                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8248                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8249                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8250                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8251                             checkCnt++;
8252                         if(checkCnt >= 2) {
8253                             reason = "Xboard adjudication: 3rd check";
8254                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8255                             break;
8256                         }
8257                     }
8258                 }
8259               case MT_NONE:
8260               default:
8261                 break;
8262               case MT_STEALMATE:
8263               case MT_STALEMATE:
8264               case MT_STAINMATE:
8265                 reason = "Xboard adjudication: Stalemate";
8266                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8267                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8268                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8269                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8270                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8271                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8272                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8273                                                                         EP_CHECKMATE : EP_WINS);
8274                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8275                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8276                 }
8277                 break;
8278               case MT_CHECKMATE:
8279                 reason = "Xboard adjudication: Checkmate";
8280                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8281                 if(gameInfo.variant == VariantShogi) {
8282                     if(forwardMostMove > backwardMostMove
8283                        && moveList[forwardMostMove-1][1] == '@'
8284                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8285                         reason = "XBoard adjudication: pawn-drop mate";
8286                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8287                     }
8288                 }
8289                 break;
8290             }
8291
8292                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8293                     case EP_STALEMATE:
8294                         result = GameIsDrawn; break;
8295                     case EP_CHECKMATE:
8296                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8297                     case EP_WINS:
8298                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8299                     default:
8300                         result = EndOfFile;
8301                 }
8302                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8303                     if(engineOpponent)
8304                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8305                     GameEnds( result, reason, GE_XBOARD );
8306                     return 1;
8307                 }
8308
8309                 /* Next absolutely insufficient mating material. */
8310                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8311                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8312                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8313
8314                      /* always flag draws, for judging claims */
8315                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8316
8317                      if(canAdjudicate && appData.materialDraws) {
8318                          /* but only adjudicate them if adjudication enabled */
8319                          if(engineOpponent) {
8320                            SendToProgram("force\n", engineOpponent); // suppress reply
8321                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8322                          }
8323                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8324                          return 1;
8325                      }
8326                 }
8327
8328                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8329                 if(gameInfo.variant == VariantXiangqi ?
8330                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8331                  : nrW + nrB == 4 &&
8332                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8333                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8334                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8335                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8336                    ) ) {
8337                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8338                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8339                           if(engineOpponent) {
8340                             SendToProgram("force\n", engineOpponent); // suppress reply
8341                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8342                           }
8343                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8344                           return 1;
8345                      }
8346                 } else moveCount = 6;
8347             }
8348
8349         // Repetition draws and 50-move rule can be applied independently of legality testing
8350
8351                 /* Check for rep-draws */
8352                 count = 0;
8353                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8354                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8355                 for(k = forwardMostMove-2;
8356                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8357                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8358                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8359                     k-=2)
8360                 {   int rights=0;
8361                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8362                         /* compare castling rights */
8363                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8364                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8365                                 rights++; /* King lost rights, while rook still had them */
8366                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8367                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8368                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8369                                    rights++; /* but at least one rook lost them */
8370                         }
8371                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8372                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8373                                 rights++;
8374                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8375                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8376                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8377                                    rights++;
8378                         }
8379                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8380                             && appData.drawRepeats > 1) {
8381                              /* adjudicate after user-specified nr of repeats */
8382                              int result = GameIsDrawn;
8383                              char *details = "XBoard adjudication: repetition draw";
8384                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8385                                 // [HGM] xiangqi: check for forbidden perpetuals
8386                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8387                                 for(m=forwardMostMove; m>k; m-=2) {
8388                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8389                                         ourPerpetual = 0; // the current mover did not always check
8390                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8391                                         hisPerpetual = 0; // the opponent did not always check
8392                                 }
8393                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8394                                                                         ourPerpetual, hisPerpetual);
8395                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8396                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8397                                     details = "Xboard adjudication: perpetual checking";
8398                                 } else
8399                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8400                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8401                                 } else
8402                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8403                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8404                                         result = BlackWins;
8405                                         details = "Xboard adjudication: repetition";
8406                                     }
8407                                 } else // it must be XQ
8408                                 // Now check for perpetual chases
8409                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8410                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8411                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8412                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8413                                         static char resdet[MSG_SIZ];
8414                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8415                                         details = resdet;
8416                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8417                                     } else
8418                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8419                                         break; // Abort repetition-checking loop.
8420                                 }
8421                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8422                              }
8423                              if(engineOpponent) {
8424                                SendToProgram("force\n", engineOpponent); // suppress reply
8425                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8426                              }
8427                              GameEnds( result, details, GE_XBOARD );
8428                              return 1;
8429                         }
8430                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8431                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8432                     }
8433                 }
8434
8435                 /* Now we test for 50-move draws. Determine ply count */
8436                 count = forwardMostMove;
8437                 /* look for last irreversble move */
8438                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8439                     count--;
8440                 /* if we hit starting position, add initial plies */
8441                 if( count == backwardMostMove )
8442                     count -= initialRulePlies;
8443                 count = forwardMostMove - count;
8444                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8445                         // adjust reversible move counter for checks in Xiangqi
8446                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8447                         if(i < backwardMostMove) i = backwardMostMove;
8448                         while(i <= forwardMostMove) {
8449                                 lastCheck = inCheck; // check evasion does not count
8450                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8451                                 if(inCheck || lastCheck) count--; // check does not count
8452                                 i++;
8453                         }
8454                 }
8455                 if( count >= 100)
8456                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8457                          /* this is used to judge if draw claims are legal */
8458                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8459                          if(engineOpponent) {
8460                            SendToProgram("force\n", engineOpponent); // suppress reply
8461                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8462                          }
8463                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8464                          return 1;
8465                 }
8466
8467                 /* if draw offer is pending, treat it as a draw claim
8468                  * when draw condition present, to allow engines a way to
8469                  * claim draws before making their move to avoid a race
8470                  * condition occurring after their move
8471                  */
8472                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8473                          char *p = NULL;
8474                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8475                              p = "Draw claim: 50-move rule";
8476                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8477                              p = "Draw claim: 3-fold repetition";
8478                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8479                              p = "Draw claim: insufficient mating material";
8480                          if( p != NULL && canAdjudicate) {
8481                              if(engineOpponent) {
8482                                SendToProgram("force\n", engineOpponent); // suppress reply
8483                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8484                              }
8485                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8486                              return 1;
8487                          }
8488                 }
8489
8490                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8491                     if(engineOpponent) {
8492                       SendToProgram("force\n", engineOpponent); // suppress reply
8493                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8494                     }
8495                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8496                     return 1;
8497                 }
8498         return 0;
8499 }
8500
8501 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8502 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8503 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8504
8505 static int
8506 BitbaseProbe ()
8507 {
8508     int pieces[10], squares[10], cnt=0, r, f, res;
8509     static int loaded;
8510     static PPROBE_EGBB probeBB;
8511     if(!appData.testLegality) return 10;
8512     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8513     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8514     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8515     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8516         ChessSquare piece = boards[forwardMostMove][r][f];
8517         int black = (piece >= BlackPawn);
8518         int type = piece - black*BlackPawn;
8519         if(piece == EmptySquare) continue;
8520         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8521         if(type == WhiteKing) type = WhiteQueen + 1;
8522         type = egbbCode[type];
8523         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8524         pieces[cnt] = type + black*6;
8525         if(++cnt > 5) return 11;
8526     }
8527     pieces[cnt] = squares[cnt] = 0;
8528     // probe EGBB
8529     if(loaded == 2) return 13; // loading failed before
8530     if(loaded == 0) {
8531         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8532         HMODULE lib;
8533         PLOAD_EGBB loadBB;
8534         loaded = 2; // prepare for failure
8535         if(!path) return 13; // no egbb installed
8536         strncpy(buf, path + 8, MSG_SIZ);
8537         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8538         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8539         lib = LoadLibrary(buf);
8540         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8541         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8542         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8543         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8544         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8545         loaded = 1; // success!
8546     }
8547     res = probeBB(forwardMostMove & 1, pieces, squares);
8548     return res > 0 ? 1 : res < 0 ? -1 : 0;
8549 }
8550
8551 char *
8552 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8553 {   // [HGM] book: this routine intercepts moves to simulate book replies
8554     char *bookHit = NULL;
8555
8556     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8557         char buf[MSG_SIZ];
8558         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8559         SendToProgram(buf, cps);
8560     }
8561     //first determine if the incoming move brings opponent into his book
8562     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8563         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8564     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8565     if(bookHit != NULL && !cps->bookSuspend) {
8566         // make sure opponent is not going to reply after receiving move to book position
8567         SendToProgram("force\n", cps);
8568         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8569     }
8570     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8571     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8572     // now arrange restart after book miss
8573     if(bookHit) {
8574         // after a book hit we never send 'go', and the code after the call to this routine
8575         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8576         char buf[MSG_SIZ], *move = bookHit;
8577         if(cps->useSAN) {
8578             int fromX, fromY, toX, toY;
8579             char promoChar;
8580             ChessMove moveType;
8581             move = buf + 30;
8582             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8583                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8584                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8585                                     PosFlags(forwardMostMove),
8586                                     fromY, fromX, toY, toX, promoChar, move);
8587             } else {
8588                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8589                 bookHit = NULL;
8590             }
8591         }
8592         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8593         SendToProgram(buf, cps);
8594         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8595     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8596         SendToProgram("go\n", cps);
8597         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8598     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8599         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8600             SendToProgram("go\n", cps);
8601         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8602     }
8603     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8604 }
8605
8606 int
8607 LoadError (char *errmess, ChessProgramState *cps)
8608 {   // unloads engine and switches back to -ncp mode if it was first
8609     if(cps->initDone) return FALSE;
8610     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8611     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8612     cps->pr = NoProc;
8613     if(cps == &first) {
8614         appData.noChessProgram = TRUE;
8615         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8616         gameMode = BeginningOfGame; ModeHighlight();
8617         SetNCPMode();
8618     }
8619     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8620     DisplayMessage("", ""); // erase waiting message
8621     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8622     return TRUE;
8623 }
8624
8625 char *savedMessage;
8626 ChessProgramState *savedState;
8627 void
8628 DeferredBookMove (void)
8629 {
8630         if(savedState->lastPing != savedState->lastPong)
8631                     ScheduleDelayedEvent(DeferredBookMove, 10);
8632         else
8633         HandleMachineMove(savedMessage, savedState);
8634 }
8635
8636 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8637 static ChessProgramState *stalledEngine;
8638 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8639
8640 void
8641 HandleMachineMove (char *message, ChessProgramState *cps)
8642 {
8643     static char firstLeg[20];
8644     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8645     char realname[MSG_SIZ];
8646     int fromX, fromY, toX, toY;
8647     ChessMove moveType;
8648     char promoChar, roar;
8649     char *p, *pv=buf1;
8650     int oldError;
8651     char *bookHit;
8652
8653     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8654         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8655         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8656             DisplayError(_("Invalid pairing from pairing engine"), 0);
8657             return;
8658         }
8659         pairingReceived = 1;
8660         NextMatchGame();
8661         return; // Skim the pairing messages here.
8662     }
8663
8664     oldError = cps->userError; cps->userError = 0;
8665
8666 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8667     /*
8668      * Kludge to ignore BEL characters
8669      */
8670     while (*message == '\007') message++;
8671
8672     /*
8673      * [HGM] engine debug message: ignore lines starting with '#' character
8674      */
8675     if(cps->debug && *message == '#') return;
8676
8677     /*
8678      * Look for book output
8679      */
8680     if (cps == &first && bookRequested) {
8681         if (message[0] == '\t' || message[0] == ' ') {
8682             /* Part of the book output is here; append it */
8683             strcat(bookOutput, message);
8684             strcat(bookOutput, "  \n");
8685             return;
8686         } else if (bookOutput[0] != NULLCHAR) {
8687             /* All of book output has arrived; display it */
8688             char *p = bookOutput;
8689             while (*p != NULLCHAR) {
8690                 if (*p == '\t') *p = ' ';
8691                 p++;
8692             }
8693             DisplayInformation(bookOutput);
8694             bookRequested = FALSE;
8695             /* Fall through to parse the current output */
8696         }
8697     }
8698
8699     /*
8700      * Look for machine move.
8701      */
8702     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8703         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8704     {
8705         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8706             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8707             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8708             stalledEngine = cps;
8709             if(appData.ponderNextMove) { // bring opponent out of ponder
8710                 if(gameMode == TwoMachinesPlay) {
8711                     if(cps->other->pause)
8712                         PauseEngine(cps->other);
8713                     else
8714                         SendToProgram("easy\n", cps->other);
8715                 }
8716             }
8717             StopClocks();
8718             return;
8719         }
8720
8721       if(cps->usePing) {
8722
8723         /* This method is only useful on engines that support ping */
8724         if(abortEngineThink) {
8725             if (appData.debugMode) {
8726                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8727             }
8728             SendToProgram("undo\n", cps);
8729             return;
8730         }
8731
8732         if (cps->lastPing != cps->lastPong) {
8733             /* Extra move from before last new; ignore */
8734             if (appData.debugMode) {
8735                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8736             }
8737           return;
8738         }
8739
8740       } else {
8741
8742         int machineWhite = FALSE;
8743
8744         switch (gameMode) {
8745           case BeginningOfGame:
8746             /* Extra move from before last reset; ignore */
8747             if (appData.debugMode) {
8748                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8749             }
8750             return;
8751
8752           case EndOfGame:
8753           case IcsIdle:
8754           default:
8755             /* Extra move after we tried to stop.  The mode test is
8756                not a reliable way of detecting this problem, but it's
8757                the best we can do on engines that don't support ping.
8758             */
8759             if (appData.debugMode) {
8760                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8761                         cps->which, gameMode);
8762             }
8763             SendToProgram("undo\n", cps);
8764             return;
8765
8766           case MachinePlaysWhite:
8767           case IcsPlayingWhite:
8768             machineWhite = TRUE;
8769             break;
8770
8771           case MachinePlaysBlack:
8772           case IcsPlayingBlack:
8773             machineWhite = FALSE;
8774             break;
8775
8776           case TwoMachinesPlay:
8777             machineWhite = (cps->twoMachinesColor[0] == 'w');
8778             break;
8779         }
8780         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8781             if (appData.debugMode) {
8782                 fprintf(debugFP,
8783                         "Ignoring move out of turn by %s, gameMode %d"
8784                         ", forwardMost %d\n",
8785                         cps->which, gameMode, forwardMostMove);
8786             }
8787             return;
8788         }
8789       }
8790
8791         if(cps->alphaRank) AlphaRank(machineMove, 4);
8792
8793         // [HGM] lion: (some very limited) support for Alien protocol
8794         killX = killY = kill2X = kill2Y = -1;
8795         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8796             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8797             return;
8798         }
8799         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8800             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8801             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8802         }
8803         if(firstLeg[0]) { // there was a previous leg;
8804             // only support case where same piece makes two step
8805             char buf[20], *p = machineMove+1, *q = buf+1, f;
8806             safeStrCpy(buf, machineMove, 20);
8807             while(isdigit(*q)) q++; // find start of to-square
8808             safeStrCpy(machineMove, firstLeg, 20);
8809             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8810             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8811             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8812             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8813             firstLeg[0] = NULLCHAR;
8814         }
8815
8816         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8817                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8818             /* Machine move could not be parsed; ignore it. */
8819           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8820                     machineMove, _(cps->which));
8821             DisplayMoveError(buf1);
8822             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8823                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8824             if (gameMode == TwoMachinesPlay) {
8825               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8826                        buf1, GE_XBOARD);
8827             }
8828             return;
8829         }
8830
8831         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8832         /* So we have to redo legality test with true e.p. status here,  */
8833         /* to make sure an illegal e.p. capture does not slip through,   */
8834         /* to cause a forfeit on a justified illegal-move complaint      */
8835         /* of the opponent.                                              */
8836         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8837            ChessMove moveType;
8838            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8839                              fromY, fromX, toY, toX, promoChar);
8840             if(moveType == IllegalMove) {
8841               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8842                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8843                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8844                            buf1, GE_XBOARD);
8845                 return;
8846            } else if(!appData.fischerCastling)
8847            /* [HGM] Kludge to handle engines that send FRC-style castling
8848               when they shouldn't (like TSCP-Gothic) */
8849            switch(moveType) {
8850              case WhiteASideCastleFR:
8851              case BlackASideCastleFR:
8852                toX+=2;
8853                currentMoveString[2]++;
8854                break;
8855              case WhiteHSideCastleFR:
8856              case BlackHSideCastleFR:
8857                toX--;
8858                currentMoveString[2]--;
8859                break;
8860              default: ; // nothing to do, but suppresses warning of pedantic compilers
8861            }
8862         }
8863         hintRequested = FALSE;
8864         lastHint[0] = NULLCHAR;
8865         bookRequested = FALSE;
8866         /* Program may be pondering now */
8867         cps->maybeThinking = TRUE;
8868         if (cps->sendTime == 2) cps->sendTime = 1;
8869         if (cps->offeredDraw) cps->offeredDraw--;
8870
8871         /* [AS] Save move info*/
8872         pvInfoList[ forwardMostMove ].score = programStats.score;
8873         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8874         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8875
8876         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8877
8878         /* Test suites abort the 'game' after one move */
8879         if(*appData.finger) {
8880            static FILE *f;
8881            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8882            if(!f) f = fopen(appData.finger, "w");
8883            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8884            else { DisplayFatalError("Bad output file", errno, 0); return; }
8885            free(fen);
8886            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8887         }
8888         if(appData.epd) {
8889            if(solvingTime >= 0) {
8890               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8891               totalTime += solvingTime; first.matchWins++;
8892            } else {
8893               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8894               second.matchWins++;
8895            }
8896            OutputKibitz(2, buf1);
8897            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8898         }
8899
8900         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8901         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8902             int count = 0;
8903
8904             while( count < adjudicateLossPlies ) {
8905                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8906
8907                 if( count & 1 ) {
8908                     score = -score; /* Flip score for winning side */
8909                 }
8910
8911                 if( score > appData.adjudicateLossThreshold ) {
8912                     break;
8913                 }
8914
8915                 count++;
8916             }
8917
8918             if( count >= adjudicateLossPlies ) {
8919                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8920
8921                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8922                     "Xboard adjudication",
8923                     GE_XBOARD );
8924
8925                 return;
8926             }
8927         }
8928
8929         if(Adjudicate(cps)) {
8930             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8931             return; // [HGM] adjudicate: for all automatic game ends
8932         }
8933
8934 #if ZIPPY
8935         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8936             first.initDone) {
8937           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8938                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8939                 SendToICS("draw ");
8940                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8941           }
8942           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8943           ics_user_moved = 1;
8944           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8945                 char buf[3*MSG_SIZ];
8946
8947                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8948                         programStats.score / 100.,
8949                         programStats.depth,
8950                         programStats.time / 100.,
8951                         (unsigned int)programStats.nodes,
8952                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8953                         programStats.movelist);
8954                 SendToICS(buf);
8955           }
8956         }
8957 #endif
8958
8959         /* [AS] Clear stats for next move */
8960         ClearProgramStats();
8961         thinkOutput[0] = NULLCHAR;
8962         hiddenThinkOutputState = 0;
8963
8964         bookHit = NULL;
8965         if (gameMode == TwoMachinesPlay) {
8966             /* [HGM] relaying draw offers moved to after reception of move */
8967             /* and interpreting offer as claim if it brings draw condition */
8968             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8969                 SendToProgram("draw\n", cps->other);
8970             }
8971             if (cps->other->sendTime) {
8972                 SendTimeRemaining(cps->other,
8973                                   cps->other->twoMachinesColor[0] == 'w');
8974             }
8975             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8976             if (firstMove && !bookHit) {
8977                 firstMove = FALSE;
8978                 if (cps->other->useColors) {
8979                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8980                 }
8981                 SendToProgram("go\n", cps->other);
8982             }
8983             cps->other->maybeThinking = TRUE;
8984         }
8985
8986         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8987
8988         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8989
8990         if (!pausing && appData.ringBellAfterMoves) {
8991             if(!roar) RingBell();
8992         }
8993
8994         /*
8995          * Reenable menu items that were disabled while
8996          * machine was thinking
8997          */
8998         if (gameMode != TwoMachinesPlay)
8999             SetUserThinkingEnables();
9000
9001         // [HGM] book: after book hit opponent has received move and is now in force mode
9002         // force the book reply into it, and then fake that it outputted this move by jumping
9003         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9004         if(bookHit) {
9005                 static char bookMove[MSG_SIZ]; // a bit generous?
9006
9007                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9008                 strcat(bookMove, bookHit);
9009                 message = bookMove;
9010                 cps = cps->other;
9011                 programStats.nodes = programStats.depth = programStats.time =
9012                 programStats.score = programStats.got_only_move = 0;
9013                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9014
9015                 if(cps->lastPing != cps->lastPong) {
9016                     savedMessage = message; // args for deferred call
9017                     savedState = cps;
9018                     ScheduleDelayedEvent(DeferredBookMove, 10);
9019                     return;
9020                 }
9021                 goto FakeBookMove;
9022         }
9023
9024         return;
9025     }
9026
9027     /* Set special modes for chess engines.  Later something general
9028      *  could be added here; for now there is just one kludge feature,
9029      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9030      *  when "xboard" is given as an interactive command.
9031      */
9032     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9033         cps->useSigint = FALSE;
9034         cps->useSigterm = FALSE;
9035     }
9036     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9037       ParseFeatures(message+8, cps);
9038       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9039     }
9040
9041     if (!strncmp(message, "setup ", 6) && 
9042         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9043           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9044                                         ) { // [HGM] allow first engine to define opening position
9045       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9046       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9047       *buf = NULLCHAR;
9048       if(sscanf(message, "setup (%s", buf) == 1) {
9049         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9050         ASSIGN(appData.pieceToCharTable, buf);
9051       }
9052       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9053       if(dummy >= 3) {
9054         while(message[s] && message[s++] != ' ');
9055         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9056            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9057             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9058             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9059           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9060           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9061           startedFromSetupPosition = FALSE;
9062         }
9063       }
9064       if(startedFromSetupPosition) return;
9065       ParseFEN(boards[0], &dummy, message+s, FALSE);
9066       DrawPosition(TRUE, boards[0]);
9067       CopyBoard(initialPosition, boards[0]);
9068       startedFromSetupPosition = TRUE;
9069       return;
9070     }
9071     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9072       ChessSquare piece = WhitePawn;
9073       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9074       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9075       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9076       piece += CharToPiece(ID & 255) - WhitePawn;
9077       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9078       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9079       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9080       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9081       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9082       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9083                                                && gameInfo.variant != VariantGreat
9084                                                && gameInfo.variant != VariantFairy    ) return;
9085       if(piece < EmptySquare) {
9086         pieceDefs = TRUE;
9087         ASSIGN(pieceDesc[piece], buf1);
9088         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9089       }
9090       return;
9091     }
9092     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9093       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9094       Sweep(0);
9095       return;
9096     }
9097     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9098      * want this, I was asked to put it in, and obliged.
9099      */
9100     if (!strncmp(message, "setboard ", 9)) {
9101         Board initial_position;
9102
9103         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9104
9105         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9106             DisplayError(_("Bad FEN received from engine"), 0);
9107             return ;
9108         } else {
9109            Reset(TRUE, FALSE);
9110            CopyBoard(boards[0], initial_position);
9111            initialRulePlies = FENrulePlies;
9112            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9113            else gameMode = MachinePlaysBlack;
9114            DrawPosition(FALSE, boards[currentMove]);
9115         }
9116         return;
9117     }
9118
9119     /*
9120      * Look for communication commands
9121      */
9122     if (!strncmp(message, "telluser ", 9)) {
9123         if(message[9] == '\\' && message[10] == '\\')
9124             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9125         PlayTellSound();
9126         DisplayNote(message + 9);
9127         return;
9128     }
9129     if (!strncmp(message, "tellusererror ", 14)) {
9130         cps->userError = 1;
9131         if(message[14] == '\\' && message[15] == '\\')
9132             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9133         PlayTellSound();
9134         DisplayError(message + 14, 0);
9135         return;
9136     }
9137     if (!strncmp(message, "tellopponent ", 13)) {
9138       if (appData.icsActive) {
9139         if (loggedOn) {
9140           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9141           SendToICS(buf1);
9142         }
9143       } else {
9144         DisplayNote(message + 13);
9145       }
9146       return;
9147     }
9148     if (!strncmp(message, "tellothers ", 11)) {
9149       if (appData.icsActive) {
9150         if (loggedOn) {
9151           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9152           SendToICS(buf1);
9153         }
9154       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9155       return;
9156     }
9157     if (!strncmp(message, "tellall ", 8)) {
9158       if (appData.icsActive) {
9159         if (loggedOn) {
9160           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9161           SendToICS(buf1);
9162         }
9163       } else {
9164         DisplayNote(message + 8);
9165       }
9166       return;
9167     }
9168     if (strncmp(message, "warning", 7) == 0) {
9169         /* Undocumented feature, use tellusererror in new code */
9170         DisplayError(message, 0);
9171         return;
9172     }
9173     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9174         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9175         strcat(realname, " query");
9176         AskQuestion(realname, buf2, buf1, cps->pr);
9177         return;
9178     }
9179     /* Commands from the engine directly to ICS.  We don't allow these to be
9180      *  sent until we are logged on. Crafty kibitzes have been known to
9181      *  interfere with the login process.
9182      */
9183     if (loggedOn) {
9184         if (!strncmp(message, "tellics ", 8)) {
9185             SendToICS(message + 8);
9186             SendToICS("\n");
9187             return;
9188         }
9189         if (!strncmp(message, "tellicsnoalias ", 15)) {
9190             SendToICS(ics_prefix);
9191             SendToICS(message + 15);
9192             SendToICS("\n");
9193             return;
9194         }
9195         /* The following are for backward compatibility only */
9196         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9197             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9198             SendToICS(ics_prefix);
9199             SendToICS(message);
9200             SendToICS("\n");
9201             return;
9202         }
9203     }
9204     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9205         if(initPing == cps->lastPong) {
9206             if(gameInfo.variant == VariantUnknown) {
9207                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9208                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9209                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9210             }
9211             initPing = -1;
9212         }
9213         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9214             abortEngineThink = FALSE;
9215             DisplayMessage("", "");
9216             ThawUI();
9217         }
9218         return;
9219     }
9220     if(!strncmp(message, "highlight ", 10)) {
9221         if(appData.testLegality && !*engineVariant && appData.markers) return;
9222         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9223         return;
9224     }
9225     if(!strncmp(message, "click ", 6)) {
9226         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9227         if(appData.testLegality || !appData.oneClick) return;
9228         sscanf(message+6, "%c%d%c", &f, &y, &c);
9229         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9230         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9231         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9232         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9233         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9234         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9235             LeftClick(Release, lastLeftX, lastLeftY);
9236         controlKey  = (c == ',');
9237         LeftClick(Press, x, y);
9238         LeftClick(Release, x, y);
9239         first.highlight = f;
9240         return;
9241     }
9242     /*
9243      * If the move is illegal, cancel it and redraw the board.
9244      * Also deal with other error cases.  Matching is rather loose
9245      * here to accommodate engines written before the spec.
9246      */
9247     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9248         strncmp(message, "Error", 5) == 0) {
9249         if (StrStr(message, "name") ||
9250             StrStr(message, "rating") || StrStr(message, "?") ||
9251             StrStr(message, "result") || StrStr(message, "board") ||
9252             StrStr(message, "bk") || StrStr(message, "computer") ||
9253             StrStr(message, "variant") || StrStr(message, "hint") ||
9254             StrStr(message, "random") || StrStr(message, "depth") ||
9255             StrStr(message, "accepted")) {
9256             return;
9257         }
9258         if (StrStr(message, "protover")) {
9259           /* Program is responding to input, so it's apparently done
9260              initializing, and this error message indicates it is
9261              protocol version 1.  So we don't need to wait any longer
9262              for it to initialize and send feature commands. */
9263           FeatureDone(cps, 1);
9264           cps->protocolVersion = 1;
9265           return;
9266         }
9267         cps->maybeThinking = FALSE;
9268
9269         if (StrStr(message, "draw")) {
9270             /* Program doesn't have "draw" command */
9271             cps->sendDrawOffers = 0;
9272             return;
9273         }
9274         if (cps->sendTime != 1 &&
9275             (StrStr(message, "time") || StrStr(message, "otim"))) {
9276           /* Program apparently doesn't have "time" or "otim" command */
9277           cps->sendTime = 0;
9278           return;
9279         }
9280         if (StrStr(message, "analyze")) {
9281             cps->analysisSupport = FALSE;
9282             cps->analyzing = FALSE;
9283 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9284             EditGameEvent(); // [HGM] try to preserve loaded game
9285             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9286             DisplayError(buf2, 0);
9287             return;
9288         }
9289         if (StrStr(message, "(no matching move)st")) {
9290           /* Special kludge for GNU Chess 4 only */
9291           cps->stKludge = TRUE;
9292           SendTimeControl(cps, movesPerSession, timeControl,
9293                           timeIncrement, appData.searchDepth,
9294                           searchTime);
9295           return;
9296         }
9297         if (StrStr(message, "(no matching move)sd")) {
9298           /* Special kludge for GNU Chess 4 only */
9299           cps->sdKludge = TRUE;
9300           SendTimeControl(cps, movesPerSession, timeControl,
9301                           timeIncrement, appData.searchDepth,
9302                           searchTime);
9303           return;
9304         }
9305         if (!StrStr(message, "llegal")) {
9306             return;
9307         }
9308         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9309             gameMode == IcsIdle) return;
9310         if (forwardMostMove <= backwardMostMove) return;
9311         if (pausing) PauseEvent();
9312       if(appData.forceIllegal) {
9313             // [HGM] illegal: machine refused move; force position after move into it
9314           SendToProgram("force\n", cps);
9315           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9316                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9317                 // when black is to move, while there might be nothing on a2 or black
9318                 // might already have the move. So send the board as if white has the move.
9319                 // But first we must change the stm of the engine, as it refused the last move
9320                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9321                 if(WhiteOnMove(forwardMostMove)) {
9322                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9323                     SendBoard(cps, forwardMostMove); // kludgeless board
9324                 } else {
9325                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9326                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9327                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9328                 }
9329           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9330             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9331                  gameMode == TwoMachinesPlay)
9332               SendToProgram("go\n", cps);
9333             return;
9334       } else
9335         if (gameMode == PlayFromGameFile) {
9336             /* Stop reading this game file */
9337             gameMode = EditGame;
9338             ModeHighlight();
9339         }
9340         /* [HGM] illegal-move claim should forfeit game when Xboard */
9341         /* only passes fully legal moves                            */
9342         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9343             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9344                                 "False illegal-move claim", GE_XBOARD );
9345             return; // do not take back move we tested as valid
9346         }
9347         currentMove = forwardMostMove-1;
9348         DisplayMove(currentMove-1); /* before DisplayMoveError */
9349         SwitchClocks(forwardMostMove-1); // [HGM] race
9350         DisplayBothClocks();
9351         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9352                 parseList[currentMove], _(cps->which));
9353         DisplayMoveError(buf1);
9354         DrawPosition(FALSE, boards[currentMove]);
9355
9356         SetUserThinkingEnables();
9357         return;
9358     }
9359     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9360         /* Program has a broken "time" command that
9361            outputs a string not ending in newline.
9362            Don't use it. */
9363         cps->sendTime = 0;
9364     }
9365     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9366         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9367             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9368     }
9369
9370     /*
9371      * If chess program startup fails, exit with an error message.
9372      * Attempts to recover here are futile. [HGM] Well, we try anyway
9373      */
9374     if ((StrStr(message, "unknown host") != NULL)
9375         || (StrStr(message, "No remote directory") != NULL)
9376         || (StrStr(message, "not found") != NULL)
9377         || (StrStr(message, "No such file") != NULL)
9378         || (StrStr(message, "can't alloc") != NULL)
9379         || (StrStr(message, "Permission denied") != NULL)) {
9380
9381         cps->maybeThinking = FALSE;
9382         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9383                 _(cps->which), cps->program, cps->host, message);
9384         RemoveInputSource(cps->isr);
9385         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9386             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9387             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9388         }
9389         return;
9390     }
9391
9392     /*
9393      * Look for hint output
9394      */
9395     if (sscanf(message, "Hint: %s", buf1) == 1) {
9396         if (cps == &first && hintRequested) {
9397             hintRequested = FALSE;
9398             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9399                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9400                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9401                                     PosFlags(forwardMostMove),
9402                                     fromY, fromX, toY, toX, promoChar, buf1);
9403                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9404                 DisplayInformation(buf2);
9405             } else {
9406                 /* Hint move could not be parsed!? */
9407               snprintf(buf2, sizeof(buf2),
9408                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9409                         buf1, _(cps->which));
9410                 DisplayError(buf2, 0);
9411             }
9412         } else {
9413           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9414         }
9415         return;
9416     }
9417
9418     /*
9419      * Ignore other messages if game is not in progress
9420      */
9421     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9422         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9423
9424     /*
9425      * look for win, lose, draw, or draw offer
9426      */
9427     if (strncmp(message, "1-0", 3) == 0) {
9428         char *p, *q, *r = "";
9429         p = strchr(message, '{');
9430         if (p) {
9431             q = strchr(p, '}');
9432             if (q) {
9433                 *q = NULLCHAR;
9434                 r = p + 1;
9435             }
9436         }
9437         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9438         return;
9439     } else if (strncmp(message, "0-1", 3) == 0) {
9440         char *p, *q, *r = "";
9441         p = strchr(message, '{');
9442         if (p) {
9443             q = strchr(p, '}');
9444             if (q) {
9445                 *q = NULLCHAR;
9446                 r = p + 1;
9447             }
9448         }
9449         /* Kludge for Arasan 4.1 bug */
9450         if (strcmp(r, "Black resigns") == 0) {
9451             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9452             return;
9453         }
9454         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9455         return;
9456     } else if (strncmp(message, "1/2", 3) == 0) {
9457         char *p, *q, *r = "";
9458         p = strchr(message, '{');
9459         if (p) {
9460             q = strchr(p, '}');
9461             if (q) {
9462                 *q = NULLCHAR;
9463                 r = p + 1;
9464             }
9465         }
9466
9467         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9468         return;
9469
9470     } else if (strncmp(message, "White resign", 12) == 0) {
9471         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9472         return;
9473     } else if (strncmp(message, "Black resign", 12) == 0) {
9474         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9475         return;
9476     } else if (strncmp(message, "White matches", 13) == 0 ||
9477                strncmp(message, "Black matches", 13) == 0   ) {
9478         /* [HGM] ignore GNUShogi noises */
9479         return;
9480     } else if (strncmp(message, "White", 5) == 0 &&
9481                message[5] != '(' &&
9482                StrStr(message, "Black") == NULL) {
9483         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9484         return;
9485     } else if (strncmp(message, "Black", 5) == 0 &&
9486                message[5] != '(') {
9487         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9488         return;
9489     } else if (strcmp(message, "resign") == 0 ||
9490                strcmp(message, "computer resigns") == 0) {
9491         switch (gameMode) {
9492           case MachinePlaysBlack:
9493           case IcsPlayingBlack:
9494             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9495             break;
9496           case MachinePlaysWhite:
9497           case IcsPlayingWhite:
9498             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9499             break;
9500           case TwoMachinesPlay:
9501             if (cps->twoMachinesColor[0] == 'w')
9502               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9503             else
9504               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9505             break;
9506           default:
9507             /* can't happen */
9508             break;
9509         }
9510         return;
9511     } else if (strncmp(message, "opponent mates", 14) == 0) {
9512         switch (gameMode) {
9513           case MachinePlaysBlack:
9514           case IcsPlayingBlack:
9515             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9516             break;
9517           case MachinePlaysWhite:
9518           case IcsPlayingWhite:
9519             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9520             break;
9521           case TwoMachinesPlay:
9522             if (cps->twoMachinesColor[0] == 'w')
9523               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9524             else
9525               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9526             break;
9527           default:
9528             /* can't happen */
9529             break;
9530         }
9531         return;
9532     } else if (strncmp(message, "computer mates", 14) == 0) {
9533         switch (gameMode) {
9534           case MachinePlaysBlack:
9535           case IcsPlayingBlack:
9536             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9537             break;
9538           case MachinePlaysWhite:
9539           case IcsPlayingWhite:
9540             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9541             break;
9542           case TwoMachinesPlay:
9543             if (cps->twoMachinesColor[0] == 'w')
9544               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9545             else
9546               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9547             break;
9548           default:
9549             /* can't happen */
9550             break;
9551         }
9552         return;
9553     } else if (strncmp(message, "checkmate", 9) == 0) {
9554         if (WhiteOnMove(forwardMostMove)) {
9555             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9556         } else {
9557             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9558         }
9559         return;
9560     } else if (strstr(message, "Draw") != NULL ||
9561                strstr(message, "game is a draw") != NULL) {
9562         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9563         return;
9564     } else if (strstr(message, "offer") != NULL &&
9565                strstr(message, "draw") != NULL) {
9566 #if ZIPPY
9567         if (appData.zippyPlay && first.initDone) {
9568             /* Relay offer to ICS */
9569             SendToICS(ics_prefix);
9570             SendToICS("draw\n");
9571         }
9572 #endif
9573         cps->offeredDraw = 2; /* valid until this engine moves twice */
9574         if (gameMode == TwoMachinesPlay) {
9575             if (cps->other->offeredDraw) {
9576                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9577             /* [HGM] in two-machine mode we delay relaying draw offer      */
9578             /* until after we also have move, to see if it is really claim */
9579             }
9580         } else if (gameMode == MachinePlaysWhite ||
9581                    gameMode == MachinePlaysBlack) {
9582           if (userOfferedDraw) {
9583             DisplayInformation(_("Machine accepts your draw offer"));
9584             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9585           } else {
9586             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9587           }
9588         }
9589     }
9590
9591
9592     /*
9593      * Look for thinking output
9594      */
9595     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9596           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9597                                 ) {
9598         int plylev, mvleft, mvtot, curscore, time;
9599         char mvname[MOVE_LEN];
9600         u64 nodes; // [DM]
9601         char plyext;
9602         int ignore = FALSE;
9603         int prefixHint = FALSE;
9604         mvname[0] = NULLCHAR;
9605
9606         switch (gameMode) {
9607           case MachinePlaysBlack:
9608           case IcsPlayingBlack:
9609             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9610             break;
9611           case MachinePlaysWhite:
9612           case IcsPlayingWhite:
9613             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9614             break;
9615           case AnalyzeMode:
9616           case AnalyzeFile:
9617             break;
9618           case IcsObserving: /* [DM] icsEngineAnalyze */
9619             if (!appData.icsEngineAnalyze) ignore = TRUE;
9620             break;
9621           case TwoMachinesPlay:
9622             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9623                 ignore = TRUE;
9624             }
9625             break;
9626           default:
9627             ignore = TRUE;
9628             break;
9629         }
9630
9631         if (!ignore) {
9632             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9633             buf1[0] = NULLCHAR;
9634             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9635                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9636                 char score_buf[MSG_SIZ];
9637
9638                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9639                     nodes += u64Const(0x100000000);
9640
9641                 if (plyext != ' ' && plyext != '\t') {
9642                     time *= 100;
9643                 }
9644
9645                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9646                 if( cps->scoreIsAbsolute &&
9647                     ( gameMode == MachinePlaysBlack ||
9648                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9649                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9650                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9651                      !WhiteOnMove(currentMove)
9652                     ) )
9653                 {
9654                     curscore = -curscore;
9655                 }
9656
9657                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9658
9659                 if(*bestMove) { // rememer time best EPD move was first found
9660                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9661                     ChessMove mt;
9662                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9663                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9664                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9665                 }
9666
9667                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9668                         char buf[MSG_SIZ];
9669                         FILE *f;
9670                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9671                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9672                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9673                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9674                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9675                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9676                                 fclose(f);
9677                         }
9678                         else
9679                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9680                           DisplayError(_("failed writing PV"), 0);
9681                 }
9682
9683                 tempStats.depth = plylev;
9684                 tempStats.nodes = nodes;
9685                 tempStats.time = time;
9686                 tempStats.score = curscore;
9687                 tempStats.got_only_move = 0;
9688
9689                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9690                         int ticklen;
9691
9692                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9693                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9694                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9695                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9696                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9697                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9698                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9699                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9700                 }
9701
9702                 /* Buffer overflow protection */
9703                 if (pv[0] != NULLCHAR) {
9704                     if (strlen(pv) >= sizeof(tempStats.movelist)
9705                         && appData.debugMode) {
9706                         fprintf(debugFP,
9707                                 "PV is too long; using the first %u bytes.\n",
9708                                 (unsigned) sizeof(tempStats.movelist) - 1);
9709                     }
9710
9711                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9712                 } else {
9713                     sprintf(tempStats.movelist, " no PV\n");
9714                 }
9715
9716                 if (tempStats.seen_stat) {
9717                     tempStats.ok_to_send = 1;
9718                 }
9719
9720                 if (strchr(tempStats.movelist, '(') != NULL) {
9721                     tempStats.line_is_book = 1;
9722                     tempStats.nr_moves = 0;
9723                     tempStats.moves_left = 0;
9724                 } else {
9725                     tempStats.line_is_book = 0;
9726                 }
9727
9728                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9729                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9730
9731                 SendProgramStatsToFrontend( cps, &tempStats );
9732
9733                 /*
9734                     [AS] Protect the thinkOutput buffer from overflow... this
9735                     is only useful if buf1 hasn't overflowed first!
9736                 */
9737                 if(curscore >= MATE_SCORE) 
9738                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9739                 else if(curscore <= -MATE_SCORE) 
9740                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9741                 else
9742                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9743                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9744                          plylev,
9745                          (gameMode == TwoMachinesPlay ?
9746                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9747                          score_buf,
9748                          prefixHint ? lastHint : "",
9749                          prefixHint ? " " : "" );
9750
9751                 if( buf1[0] != NULLCHAR ) {
9752                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9753
9754                     if( strlen(pv) > max_len ) {
9755                         if( appData.debugMode) {
9756                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9757                         }
9758                         pv[max_len+1] = '\0';
9759                     }
9760
9761                     strcat( thinkOutput, pv);
9762                 }
9763
9764                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9765                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9766                     DisplayMove(currentMove - 1);
9767                 }
9768                 return;
9769
9770             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9771                 /* crafty (9.25+) says "(only move) <move>"
9772                  * if there is only 1 legal move
9773                  */
9774                 sscanf(p, "(only move) %s", buf1);
9775                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9776                 sprintf(programStats.movelist, "%s (only move)", buf1);
9777                 programStats.depth = 1;
9778                 programStats.nr_moves = 1;
9779                 programStats.moves_left = 1;
9780                 programStats.nodes = 1;
9781                 programStats.time = 1;
9782                 programStats.got_only_move = 1;
9783
9784                 /* Not really, but we also use this member to
9785                    mean "line isn't going to change" (Crafty
9786                    isn't searching, so stats won't change) */
9787                 programStats.line_is_book = 1;
9788
9789                 SendProgramStatsToFrontend( cps, &programStats );
9790
9791                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9792                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9793                     DisplayMove(currentMove - 1);
9794                 }
9795                 return;
9796             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9797                               &time, &nodes, &plylev, &mvleft,
9798                               &mvtot, mvname) >= 5) {
9799                 /* The stat01: line is from Crafty (9.29+) in response
9800                    to the "." command */
9801                 programStats.seen_stat = 1;
9802                 cps->maybeThinking = TRUE;
9803
9804                 if (programStats.got_only_move || !appData.periodicUpdates)
9805                   return;
9806
9807                 programStats.depth = plylev;
9808                 programStats.time = time;
9809                 programStats.nodes = nodes;
9810                 programStats.moves_left = mvleft;
9811                 programStats.nr_moves = mvtot;
9812                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9813                 programStats.ok_to_send = 1;
9814                 programStats.movelist[0] = '\0';
9815
9816                 SendProgramStatsToFrontend( cps, &programStats );
9817
9818                 return;
9819
9820             } else if (strncmp(message,"++",2) == 0) {
9821                 /* Crafty 9.29+ outputs this */
9822                 programStats.got_fail = 2;
9823                 return;
9824
9825             } else if (strncmp(message,"--",2) == 0) {
9826                 /* Crafty 9.29+ outputs this */
9827                 programStats.got_fail = 1;
9828                 return;
9829
9830             } else if (thinkOutput[0] != NULLCHAR &&
9831                        strncmp(message, "    ", 4) == 0) {
9832                 unsigned message_len;
9833
9834                 p = message;
9835                 while (*p && *p == ' ') p++;
9836
9837                 message_len = strlen( p );
9838
9839                 /* [AS] Avoid buffer overflow */
9840                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9841                     strcat(thinkOutput, " ");
9842                     strcat(thinkOutput, p);
9843                 }
9844
9845                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9846                     strcat(programStats.movelist, " ");
9847                     strcat(programStats.movelist, p);
9848                 }
9849
9850                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9851                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9852                     DisplayMove(currentMove - 1);
9853                 }
9854                 return;
9855             }
9856         }
9857         else {
9858             buf1[0] = NULLCHAR;
9859
9860             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9861                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9862             {
9863                 ChessProgramStats cpstats;
9864
9865                 if (plyext != ' ' && plyext != '\t') {
9866                     time *= 100;
9867                 }
9868
9869                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9870                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9871                     curscore = -curscore;
9872                 }
9873
9874                 cpstats.depth = plylev;
9875                 cpstats.nodes = nodes;
9876                 cpstats.time = time;
9877                 cpstats.score = curscore;
9878                 cpstats.got_only_move = 0;
9879                 cpstats.movelist[0] = '\0';
9880
9881                 if (buf1[0] != NULLCHAR) {
9882                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9883                 }
9884
9885                 cpstats.ok_to_send = 0;
9886                 cpstats.line_is_book = 0;
9887                 cpstats.nr_moves = 0;
9888                 cpstats.moves_left = 0;
9889
9890                 SendProgramStatsToFrontend( cps, &cpstats );
9891             }
9892         }
9893     }
9894 }
9895
9896
9897 /* Parse a game score from the character string "game", and
9898    record it as the history of the current game.  The game
9899    score is NOT assumed to start from the standard position.
9900    The display is not updated in any way.
9901    */
9902 void
9903 ParseGameHistory (char *game)
9904 {
9905     ChessMove moveType;
9906     int fromX, fromY, toX, toY, boardIndex;
9907     char promoChar;
9908     char *p, *q;
9909     char buf[MSG_SIZ];
9910
9911     if (appData.debugMode)
9912       fprintf(debugFP, "Parsing game history: %s\n", game);
9913
9914     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9915     gameInfo.site = StrSave(appData.icsHost);
9916     gameInfo.date = PGNDate();
9917     gameInfo.round = StrSave("-");
9918
9919     /* Parse out names of players */
9920     while (*game == ' ') game++;
9921     p = buf;
9922     while (*game != ' ') *p++ = *game++;
9923     *p = NULLCHAR;
9924     gameInfo.white = StrSave(buf);
9925     while (*game == ' ') game++;
9926     p = buf;
9927     while (*game != ' ' && *game != '\n') *p++ = *game++;
9928     *p = NULLCHAR;
9929     gameInfo.black = StrSave(buf);
9930
9931     /* Parse moves */
9932     boardIndex = blackPlaysFirst ? 1 : 0;
9933     yynewstr(game);
9934     for (;;) {
9935         yyboardindex = boardIndex;
9936         moveType = (ChessMove) Myylex();
9937         switch (moveType) {
9938           case IllegalMove:             /* maybe suicide chess, etc. */
9939   if (appData.debugMode) {
9940     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9941     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9942     setbuf(debugFP, NULL);
9943   }
9944           case WhitePromotion:
9945           case BlackPromotion:
9946           case WhiteNonPromotion:
9947           case BlackNonPromotion:
9948           case NormalMove:
9949           case FirstLeg:
9950           case WhiteCapturesEnPassant:
9951           case BlackCapturesEnPassant:
9952           case WhiteKingSideCastle:
9953           case WhiteQueenSideCastle:
9954           case BlackKingSideCastle:
9955           case BlackQueenSideCastle:
9956           case WhiteKingSideCastleWild:
9957           case WhiteQueenSideCastleWild:
9958           case BlackKingSideCastleWild:
9959           case BlackQueenSideCastleWild:
9960           /* PUSH Fabien */
9961           case WhiteHSideCastleFR:
9962           case WhiteASideCastleFR:
9963           case BlackHSideCastleFR:
9964           case BlackASideCastleFR:
9965           /* POP Fabien */
9966             fromX = currentMoveString[0] - AAA;
9967             fromY = currentMoveString[1] - ONE;
9968             toX = currentMoveString[2] - AAA;
9969             toY = currentMoveString[3] - ONE;
9970             promoChar = currentMoveString[4];
9971             break;
9972           case WhiteDrop:
9973           case BlackDrop:
9974             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9975             fromX = moveType == WhiteDrop ?
9976               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9977             (int) CharToPiece(ToLower(currentMoveString[0]));
9978             fromY = DROP_RANK;
9979             toX = currentMoveString[2] - AAA;
9980             toY = currentMoveString[3] - ONE;
9981             promoChar = NULLCHAR;
9982             break;
9983           case AmbiguousMove:
9984             /* bug? */
9985             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9986   if (appData.debugMode) {
9987     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9988     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9989     setbuf(debugFP, NULL);
9990   }
9991             DisplayError(buf, 0);
9992             return;
9993           case ImpossibleMove:
9994             /* bug? */
9995             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9996   if (appData.debugMode) {
9997     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9998     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9999     setbuf(debugFP, NULL);
10000   }
10001             DisplayError(buf, 0);
10002             return;
10003           case EndOfFile:
10004             if (boardIndex < backwardMostMove) {
10005                 /* Oops, gap.  How did that happen? */
10006                 DisplayError(_("Gap in move list"), 0);
10007                 return;
10008             }
10009             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10010             if (boardIndex > forwardMostMove) {
10011                 forwardMostMove = boardIndex;
10012             }
10013             return;
10014           case ElapsedTime:
10015             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10016                 strcat(parseList[boardIndex-1], " ");
10017                 strcat(parseList[boardIndex-1], yy_text);
10018             }
10019             continue;
10020           case Comment:
10021           case PGNTag:
10022           case NAG:
10023           default:
10024             /* ignore */
10025             continue;
10026           case WhiteWins:
10027           case BlackWins:
10028           case GameIsDrawn:
10029           case GameUnfinished:
10030             if (gameMode == IcsExamining) {
10031                 if (boardIndex < backwardMostMove) {
10032                     /* Oops, gap.  How did that happen? */
10033                     return;
10034                 }
10035                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10036                 return;
10037             }
10038             gameInfo.result = moveType;
10039             p = strchr(yy_text, '{');
10040             if (p == NULL) p = strchr(yy_text, '(');
10041             if (p == NULL) {
10042                 p = yy_text;
10043                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10044             } else {
10045                 q = strchr(p, *p == '{' ? '}' : ')');
10046                 if (q != NULL) *q = NULLCHAR;
10047                 p++;
10048             }
10049             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10050             gameInfo.resultDetails = StrSave(p);
10051             continue;
10052         }
10053         if (boardIndex >= forwardMostMove &&
10054             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10055             backwardMostMove = blackPlaysFirst ? 1 : 0;
10056             return;
10057         }
10058         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10059                                  fromY, fromX, toY, toX, promoChar,
10060                                  parseList[boardIndex]);
10061         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10062         /* currentMoveString is set as a side-effect of yylex */
10063         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10064         strcat(moveList[boardIndex], "\n");
10065         boardIndex++;
10066         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10067         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10068           case MT_NONE:
10069           case MT_STALEMATE:
10070           default:
10071             break;
10072           case MT_CHECK:
10073             if(!IS_SHOGI(gameInfo.variant))
10074                 strcat(parseList[boardIndex - 1], "+");
10075             break;
10076           case MT_CHECKMATE:
10077           case MT_STAINMATE:
10078             strcat(parseList[boardIndex - 1], "#");
10079             break;
10080         }
10081     }
10082 }
10083
10084
10085 /* Apply a move to the given board  */
10086 void
10087 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10088 {
10089   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10090   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10091
10092     /* [HGM] compute & store e.p. status and castling rights for new position */
10093     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10094
10095       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10096       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10097       board[EP_STATUS] = EP_NONE;
10098       board[EP_FILE] = board[EP_RANK] = 100;
10099
10100   if (fromY == DROP_RANK) {
10101         /* must be first */
10102         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10103             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10104             return;
10105         }
10106         piece = board[toY][toX] = (ChessSquare) fromX;
10107   } else {
10108 //      ChessSquare victim;
10109       int i;
10110
10111       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10112 //           victim = board[killY][killX],
10113            killed = board[killY][killX],
10114            board[killY][killX] = EmptySquare,
10115            board[EP_STATUS] = EP_CAPTURE;
10116            if( kill2X >= 0 && kill2Y >= 0)
10117              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10118       }
10119
10120       if( board[toY][toX] != EmptySquare ) {
10121            board[EP_STATUS] = EP_CAPTURE;
10122            if( (fromX != toX || fromY != toY) && // not igui!
10123                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10124                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10125                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10126            }
10127       }
10128
10129       pawn = board[fromY][fromX];
10130       if( pawn == WhiteLance || pawn == BlackLance ) {
10131            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10132                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10133                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10134            }
10135       }
10136       if( pawn == WhitePawn ) {
10137            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10138                board[EP_STATUS] = EP_PAWN_MOVE;
10139            if( toY-fromY>=2) {
10140                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10141                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10142                         gameInfo.variant != VariantBerolina || toX < fromX)
10143                       board[EP_STATUS] = toX | berolina;
10144                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10145                         gameInfo.variant != VariantBerolina || toX > fromX)
10146                       board[EP_STATUS] = toX;
10147            }
10148       } else
10149       if( pawn == BlackPawn ) {
10150            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10151                board[EP_STATUS] = EP_PAWN_MOVE;
10152            if( toY-fromY<= -2) {
10153                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10154                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10155                         gameInfo.variant != VariantBerolina || toX < fromX)
10156                       board[EP_STATUS] = toX | berolina;
10157                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10158                         gameInfo.variant != VariantBerolina || toX > fromX)
10159                       board[EP_STATUS] = toX;
10160            }
10161        }
10162
10163        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10164        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10165        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10166        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10167
10168        for(i=0; i<nrCastlingRights; i++) {
10169            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10170               board[CASTLING][i] == toX   && castlingRank[i] == toY
10171              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10172        }
10173
10174        if(gameInfo.variant == VariantSChess) { // update virginity
10175            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10176            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10177            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10178            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10179        }
10180
10181      if (fromX == toX && fromY == toY) return;
10182
10183      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10184      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10185      if(gameInfo.variant == VariantKnightmate)
10186          king += (int) WhiteUnicorn - (int) WhiteKing;
10187
10188     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10189        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10190         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10191         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10192         board[EP_STATUS] = EP_NONE; // capture was fake!
10193     } else
10194     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10195         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10196         board[toY][toX] = piece;
10197         board[EP_STATUS] = EP_NONE; // capture was fake!
10198     } else
10199     /* Code added by Tord: */
10200     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10201     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10202         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10203       board[EP_STATUS] = EP_NONE; // capture was fake!
10204       board[fromY][fromX] = EmptySquare;
10205       board[toY][toX] = EmptySquare;
10206       if((toX > fromX) != (piece == WhiteRook)) {
10207         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10208       } else {
10209         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10210       }
10211     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10212                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10213       board[EP_STATUS] = EP_NONE;
10214       board[fromY][fromX] = EmptySquare;
10215       board[toY][toX] = EmptySquare;
10216       if((toX > fromX) != (piece == BlackRook)) {
10217         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10218       } else {
10219         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10220       }
10221     /* End of code added by Tord */
10222
10223     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10224         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10225         board[toY][toX] = piece;
10226     } else if (board[fromY][fromX] == king
10227         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10228         && toY == fromY && toX > fromX+1) {
10229         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10230         board[fromY][toX-1] = board[fromY][rookX];
10231         board[fromY][rookX] = EmptySquare;
10232         board[fromY][fromX] = EmptySquare;
10233         board[toY][toX] = king;
10234     } else if (board[fromY][fromX] == king
10235         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10236                && toY == fromY && toX < fromX-1) {
10237         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10238         board[fromY][toX+1] = board[fromY][rookX];
10239         board[fromY][rookX] = EmptySquare;
10240         board[fromY][fromX] = EmptySquare;
10241         board[toY][toX] = king;
10242     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10243                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10244                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10245                ) {
10246         /* white pawn promotion */
10247         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10248         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10249             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10250         board[fromY][fromX] = EmptySquare;
10251     } else if ((fromY >= BOARD_HEIGHT>>1)
10252                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10253                && (toX != fromX)
10254                && gameInfo.variant != VariantXiangqi
10255                && gameInfo.variant != VariantBerolina
10256                && (pawn == WhitePawn)
10257                && (board[toY][toX] == EmptySquare)) {
10258         board[fromY][fromX] = EmptySquare;
10259         board[toY][toX] = piece;
10260         if(toY == epRank - 128 + 1)
10261             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10262         else
10263             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10264     } else if ((fromY == BOARD_HEIGHT-4)
10265                && (toX == fromX)
10266                && gameInfo.variant == VariantBerolina
10267                && (board[fromY][fromX] == WhitePawn)
10268                && (board[toY][toX] == EmptySquare)) {
10269         board[fromY][fromX] = EmptySquare;
10270         board[toY][toX] = WhitePawn;
10271         if(oldEP & EP_BEROLIN_A) {
10272                 captured = board[fromY][fromX-1];
10273                 board[fromY][fromX-1] = EmptySquare;
10274         }else{  captured = board[fromY][fromX+1];
10275                 board[fromY][fromX+1] = EmptySquare;
10276         }
10277     } else if (board[fromY][fromX] == king
10278         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10279                && toY == fromY && toX > fromX+1) {
10280         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10281         board[fromY][toX-1] = board[fromY][rookX];
10282         board[fromY][rookX] = EmptySquare;
10283         board[fromY][fromX] = EmptySquare;
10284         board[toY][toX] = king;
10285     } else if (board[fromY][fromX] == king
10286         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10287                && toY == fromY && toX < fromX-1) {
10288         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10289         board[fromY][toX+1] = board[fromY][rookX];
10290         board[fromY][rookX] = EmptySquare;
10291         board[fromY][fromX] = EmptySquare;
10292         board[toY][toX] = king;
10293     } else if (fromY == 7 && fromX == 3
10294                && board[fromY][fromX] == BlackKing
10295                && toY == 7 && toX == 5) {
10296         board[fromY][fromX] = EmptySquare;
10297         board[toY][toX] = BlackKing;
10298         board[fromY][7] = EmptySquare;
10299         board[toY][4] = BlackRook;
10300     } else if (fromY == 7 && fromX == 3
10301                && board[fromY][fromX] == BlackKing
10302                && toY == 7 && toX == 1) {
10303         board[fromY][fromX] = EmptySquare;
10304         board[toY][toX] = BlackKing;
10305         board[fromY][0] = EmptySquare;
10306         board[toY][2] = BlackRook;
10307     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10308                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10309                && toY < promoRank && promoChar
10310                ) {
10311         /* black pawn promotion */
10312         board[toY][toX] = CharToPiece(ToLower(promoChar));
10313         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10314             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10315         board[fromY][fromX] = EmptySquare;
10316     } else if ((fromY < BOARD_HEIGHT>>1)
10317                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10318                && (toX != fromX)
10319                && gameInfo.variant != VariantXiangqi
10320                && gameInfo.variant != VariantBerolina
10321                && (pawn == BlackPawn)
10322                && (board[toY][toX] == EmptySquare)) {
10323         board[fromY][fromX] = EmptySquare;
10324         board[toY][toX] = piece;
10325         if(toY == epRank - 128 - 1)
10326             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10327         else
10328             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10329     } else if ((fromY == 3)
10330                && (toX == fromX)
10331                && gameInfo.variant == VariantBerolina
10332                && (board[fromY][fromX] == BlackPawn)
10333                && (board[toY][toX] == EmptySquare)) {
10334         board[fromY][fromX] = EmptySquare;
10335         board[toY][toX] = BlackPawn;
10336         if(oldEP & EP_BEROLIN_A) {
10337                 captured = board[fromY][fromX-1];
10338                 board[fromY][fromX-1] = EmptySquare;
10339         }else{  captured = board[fromY][fromX+1];
10340                 board[fromY][fromX+1] = EmptySquare;
10341         }
10342     } else {
10343         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10344         board[fromY][fromX] = EmptySquare;
10345         board[toY][toX] = piece;
10346     }
10347   }
10348
10349     if (gameInfo.holdingsWidth != 0) {
10350
10351       /* !!A lot more code needs to be written to support holdings  */
10352       /* [HGM] OK, so I have written it. Holdings are stored in the */
10353       /* penultimate board files, so they are automaticlly stored   */
10354       /* in the game history.                                       */
10355       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10356                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10357         /* Delete from holdings, by decreasing count */
10358         /* and erasing image if necessary            */
10359         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10360         if(p < (int) BlackPawn) { /* white drop */
10361              p -= (int)WhitePawn;
10362                  p = PieceToNumber((ChessSquare)p);
10363              if(p >= gameInfo.holdingsSize) p = 0;
10364              if(--board[p][BOARD_WIDTH-2] <= 0)
10365                   board[p][BOARD_WIDTH-1] = EmptySquare;
10366              if((int)board[p][BOARD_WIDTH-2] < 0)
10367                         board[p][BOARD_WIDTH-2] = 0;
10368         } else {                  /* black drop */
10369              p -= (int)BlackPawn;
10370                  p = PieceToNumber((ChessSquare)p);
10371              if(p >= gameInfo.holdingsSize) p = 0;
10372              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10373                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10374              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10375                         board[BOARD_HEIGHT-1-p][1] = 0;
10376         }
10377       }
10378       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10379           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10380         /* [HGM] holdings: Add to holdings, if holdings exist */
10381         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10382                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10383                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10384         }
10385         p = (int) captured;
10386         if (p >= (int) BlackPawn) {
10387           p -= (int)BlackPawn;
10388           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10389                   /* Restore shogi-promoted piece to its original  first */
10390                   captured = (ChessSquare) (DEMOTED(captured));
10391                   p = DEMOTED(p);
10392           }
10393           p = PieceToNumber((ChessSquare)p);
10394           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10395           board[p][BOARD_WIDTH-2]++;
10396           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10397         } else {
10398           p -= (int)WhitePawn;
10399           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10400                   captured = (ChessSquare) (DEMOTED(captured));
10401                   p = DEMOTED(p);
10402           }
10403           p = PieceToNumber((ChessSquare)p);
10404           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10405           board[BOARD_HEIGHT-1-p][1]++;
10406           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10407         }
10408       }
10409     } else if (gameInfo.variant == VariantAtomic) {
10410       if (captured != EmptySquare) {
10411         int y, x;
10412         for (y = toY-1; y <= toY+1; y++) {
10413           for (x = toX-1; x <= toX+1; x++) {
10414             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10415                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10416               board[y][x] = EmptySquare;
10417             }
10418           }
10419         }
10420         board[toY][toX] = EmptySquare;
10421       }
10422     }
10423
10424     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10425         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10426     } else
10427     if(promoChar == '+') {
10428         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10429         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10430         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10431           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10432     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10433         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10434         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10435            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10436         board[toY][toX] = newPiece;
10437     }
10438     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10439                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10440         // [HGM] superchess: take promotion piece out of holdings
10441         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10442         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10443             if(!--board[k][BOARD_WIDTH-2])
10444                 board[k][BOARD_WIDTH-1] = EmptySquare;
10445         } else {
10446             if(!--board[BOARD_HEIGHT-1-k][1])
10447                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10448         }
10449     }
10450 }
10451
10452 /* Updates forwardMostMove */
10453 void
10454 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10455 {
10456     int x = toX, y = toY;
10457     char *s = parseList[forwardMostMove];
10458     ChessSquare p = boards[forwardMostMove][toY][toX];
10459 //    forwardMostMove++; // [HGM] bare: moved downstream
10460
10461     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10462     (void) CoordsToAlgebraic(boards[forwardMostMove],
10463                              PosFlags(forwardMostMove),
10464                              fromY, fromX, y, x, promoChar,
10465                              s);
10466     if(killX >= 0 && killY >= 0)
10467         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10468
10469     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10470         int timeLeft; static int lastLoadFlag=0; int king, piece;
10471         piece = boards[forwardMostMove][fromY][fromX];
10472         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10473         if(gameInfo.variant == VariantKnightmate)
10474             king += (int) WhiteUnicorn - (int) WhiteKing;
10475         if(forwardMostMove == 0) {
10476             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10477                 fprintf(serverMoves, "%s;", UserName());
10478             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10479                 fprintf(serverMoves, "%s;", second.tidy);
10480             fprintf(serverMoves, "%s;", first.tidy);
10481             if(gameMode == MachinePlaysWhite)
10482                 fprintf(serverMoves, "%s;", UserName());
10483             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10484                 fprintf(serverMoves, "%s;", second.tidy);
10485         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10486         lastLoadFlag = loadFlag;
10487         // print base move
10488         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10489         // print castling suffix
10490         if( toY == fromY && piece == king ) {
10491             if(toX-fromX > 1)
10492                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10493             if(fromX-toX >1)
10494                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10495         }
10496         // e.p. suffix
10497         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10498              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10499              boards[forwardMostMove][toY][toX] == EmptySquare
10500              && fromX != toX && fromY != toY)
10501                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10502         // promotion suffix
10503         if(promoChar != NULLCHAR) {
10504             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10505                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10506                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10507             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10508         }
10509         if(!loadFlag) {
10510                 char buf[MOVE_LEN*2], *p; int len;
10511             fprintf(serverMoves, "/%d/%d",
10512                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10513             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10514             else                      timeLeft = blackTimeRemaining/1000;
10515             fprintf(serverMoves, "/%d", timeLeft);
10516                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10517                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10518                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10519                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10520             fprintf(serverMoves, "/%s", buf);
10521         }
10522         fflush(serverMoves);
10523     }
10524
10525     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10526         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10527       return;
10528     }
10529     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10530     if (commentList[forwardMostMove+1] != NULL) {
10531         free(commentList[forwardMostMove+1]);
10532         commentList[forwardMostMove+1] = NULL;
10533     }
10534     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10535     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10536     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10537     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10538     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10539     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10540     adjustedClock = FALSE;
10541     gameInfo.result = GameUnfinished;
10542     if (gameInfo.resultDetails != NULL) {
10543         free(gameInfo.resultDetails);
10544         gameInfo.resultDetails = NULL;
10545     }
10546     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10547                               moveList[forwardMostMove - 1]);
10548     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10549       case MT_NONE:
10550       case MT_STALEMATE:
10551       default:
10552         break;
10553       case MT_CHECK:
10554         if(!IS_SHOGI(gameInfo.variant))
10555             strcat(parseList[forwardMostMove - 1], "+");
10556         break;
10557       case MT_CHECKMATE:
10558       case MT_STAINMATE:
10559         strcat(parseList[forwardMostMove - 1], "#");
10560         break;
10561     }
10562 }
10563
10564 /* Updates currentMove if not pausing */
10565 void
10566 ShowMove (int fromX, int fromY, int toX, int toY)
10567 {
10568     int instant = (gameMode == PlayFromGameFile) ?
10569         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10570     if(appData.noGUI) return;
10571     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10572         if (!instant) {
10573             if (forwardMostMove == currentMove + 1) {
10574                 AnimateMove(boards[forwardMostMove - 1],
10575                             fromX, fromY, toX, toY);
10576             }
10577         }
10578         currentMove = forwardMostMove;
10579     }
10580
10581     killX = killY = -1; // [HGM] lion: used up
10582
10583     if (instant) return;
10584
10585     DisplayMove(currentMove - 1);
10586     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10587             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10588                 SetHighlights(fromX, fromY, toX, toY);
10589             }
10590     }
10591     DrawPosition(FALSE, boards[currentMove]);
10592     DisplayBothClocks();
10593     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10594 }
10595
10596 void
10597 SendEgtPath (ChessProgramState *cps)
10598 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10599         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10600
10601         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10602
10603         while(*p) {
10604             char c, *q = name+1, *r, *s;
10605
10606             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10607             while(*p && *p != ',') *q++ = *p++;
10608             *q++ = ':'; *q = 0;
10609             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10610                 strcmp(name, ",nalimov:") == 0 ) {
10611                 // take nalimov path from the menu-changeable option first, if it is defined
10612               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10613                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10614             } else
10615             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10616                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10617                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10618                 s = r = StrStr(s, ":") + 1; // beginning of path info
10619                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10620                 c = *r; *r = 0;             // temporarily null-terminate path info
10621                     *--q = 0;               // strip of trailig ':' from name
10622                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10623                 *r = c;
10624                 SendToProgram(buf,cps);     // send egtbpath command for this format
10625             }
10626             if(*p == ',') p++; // read away comma to position for next format name
10627         }
10628 }
10629
10630 static int
10631 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10632 {
10633       int width = 8, height = 8, holdings = 0;             // most common sizes
10634       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10635       // correct the deviations default for each variant
10636       if( v == VariantXiangqi ) width = 9,  height = 10;
10637       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10638       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10639       if( v == VariantCapablanca || v == VariantCapaRandom ||
10640           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10641                                 width = 10;
10642       if( v == VariantCourier ) width = 12;
10643       if( v == VariantSuper )                            holdings = 8;
10644       if( v == VariantGreat )   width = 10,              holdings = 8;
10645       if( v == VariantSChess )                           holdings = 7;
10646       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10647       if( v == VariantChuChess) width = 10, height = 10;
10648       if( v == VariantChu )     width = 12, height = 12;
10649       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10650              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10651              holdingsSize >= 0 && holdingsSize != holdings;
10652 }
10653
10654 char variantError[MSG_SIZ];
10655
10656 char *
10657 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10658 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10659       char *p, *variant = VariantName(v);
10660       static char b[MSG_SIZ];
10661       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10662            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10663                                                holdingsSize, variant); // cook up sized variant name
10664            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10665            if(StrStr(list, b) == NULL) {
10666                // specific sized variant not known, check if general sizing allowed
10667                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10668                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10669                             boardWidth, boardHeight, holdingsSize, engine);
10670                    return NULL;
10671                }
10672                /* [HGM] here we really should compare with the maximum supported board size */
10673            }
10674       } else snprintf(b, MSG_SIZ,"%s", variant);
10675       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10676       p = StrStr(list, b);
10677       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10678       if(p == NULL) {
10679           // occurs not at all in list, or only as sub-string
10680           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10681           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10682               int l = strlen(variantError);
10683               char *q;
10684               while(p != list && p[-1] != ',') p--;
10685               q = strchr(p, ',');
10686               if(q) *q = NULLCHAR;
10687               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10688               if(q) *q= ',';
10689           }
10690           return NULL;
10691       }
10692       return b;
10693 }
10694
10695 void
10696 InitChessProgram (ChessProgramState *cps, int setup)
10697 /* setup needed to setup FRC opening position */
10698 {
10699     char buf[MSG_SIZ], *b;
10700     if (appData.noChessProgram) return;
10701     hintRequested = FALSE;
10702     bookRequested = FALSE;
10703
10704     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10705     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10706     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10707     if(cps->memSize) { /* [HGM] memory */
10708       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10709         SendToProgram(buf, cps);
10710     }
10711     SendEgtPath(cps); /* [HGM] EGT */
10712     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10713       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10714         SendToProgram(buf, cps);
10715     }
10716
10717     setboardSpoiledMachineBlack = FALSE;
10718     SendToProgram(cps->initString, cps);
10719     if (gameInfo.variant != VariantNormal &&
10720         gameInfo.variant != VariantLoadable
10721         /* [HGM] also send variant if board size non-standard */
10722         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10723
10724       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10725                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10726       if (b == NULL) {
10727         VariantClass v;
10728         char c, *q = cps->variants, *p = strchr(q, ',');
10729         if(p) *p = NULLCHAR;
10730         v = StringToVariant(q);
10731         DisplayError(variantError, 0);
10732         if(v != VariantUnknown && cps == &first) {
10733             int w, h, s;
10734             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10735                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10736             ASSIGN(appData.variant, q);
10737             Reset(TRUE, FALSE);
10738         }
10739         if(p) *p = ',';
10740         return;
10741       }
10742
10743       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10744       SendToProgram(buf, cps);
10745     }
10746     currentlyInitializedVariant = gameInfo.variant;
10747
10748     /* [HGM] send opening position in FRC to first engine */
10749     if(setup) {
10750           SendToProgram("force\n", cps);
10751           SendBoard(cps, 0);
10752           /* engine is now in force mode! Set flag to wake it up after first move. */
10753           setboardSpoiledMachineBlack = 1;
10754     }
10755
10756     if (cps->sendICS) {
10757       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10758       SendToProgram(buf, cps);
10759     }
10760     cps->maybeThinking = FALSE;
10761     cps->offeredDraw = 0;
10762     if (!appData.icsActive) {
10763         SendTimeControl(cps, movesPerSession, timeControl,
10764                         timeIncrement, appData.searchDepth,
10765                         searchTime);
10766     }
10767     if (appData.showThinking
10768         // [HGM] thinking: four options require thinking output to be sent
10769         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10770                                 ) {
10771         SendToProgram("post\n", cps);
10772     }
10773     SendToProgram("hard\n", cps);
10774     if (!appData.ponderNextMove) {
10775         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10776            it without being sure what state we are in first.  "hard"
10777            is not a toggle, so that one is OK.
10778          */
10779         SendToProgram("easy\n", cps);
10780     }
10781     if (cps->usePing) {
10782       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10783       SendToProgram(buf, cps);
10784     }
10785     cps->initDone = TRUE;
10786     ClearEngineOutputPane(cps == &second);
10787 }
10788
10789
10790 void
10791 ResendOptions (ChessProgramState *cps)
10792 { // send the stored value of the options
10793   int i;
10794   char buf[MSG_SIZ];
10795   Option *opt = cps->option;
10796   for(i=0; i<cps->nrOptions; i++, opt++) {
10797       switch(opt->type) {
10798         case Spin:
10799         case Slider:
10800         case CheckBox:
10801             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10802           break;
10803         case ComboBox:
10804           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10805           break;
10806         default:
10807             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10808           break;
10809         case Button:
10810         case SaveButton:
10811           continue;
10812       }
10813       SendToProgram(buf, cps);
10814   }
10815 }
10816
10817 void
10818 StartChessProgram (ChessProgramState *cps)
10819 {
10820     char buf[MSG_SIZ];
10821     int err;
10822
10823     if (appData.noChessProgram) return;
10824     cps->initDone = FALSE;
10825
10826     if (strcmp(cps->host, "localhost") == 0) {
10827         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10828     } else if (*appData.remoteShell == NULLCHAR) {
10829         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10830     } else {
10831         if (*appData.remoteUser == NULLCHAR) {
10832           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10833                     cps->program);
10834         } else {
10835           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10836                     cps->host, appData.remoteUser, cps->program);
10837         }
10838         err = StartChildProcess(buf, "", &cps->pr);
10839     }
10840
10841     if (err != 0) {
10842       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10843         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10844         if(cps != &first) return;
10845         appData.noChessProgram = TRUE;
10846         ThawUI();
10847         SetNCPMode();
10848 //      DisplayFatalError(buf, err, 1);
10849 //      cps->pr = NoProc;
10850 //      cps->isr = NULL;
10851         return;
10852     }
10853
10854     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10855     if (cps->protocolVersion > 1) {
10856       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10857       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10858         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10859         cps->comboCnt = 0;  //                and values of combo boxes
10860       }
10861       SendToProgram(buf, cps);
10862       if(cps->reload) ResendOptions(cps);
10863     } else {
10864       SendToProgram("xboard\n", cps);
10865     }
10866 }
10867
10868 void
10869 TwoMachinesEventIfReady P((void))
10870 {
10871   static int curMess = 0;
10872   if (first.lastPing != first.lastPong) {
10873     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10874     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10875     return;
10876   }
10877   if (second.lastPing != second.lastPong) {
10878     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10879     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10880     return;
10881   }
10882   DisplayMessage("", ""); curMess = 0;
10883   TwoMachinesEvent();
10884 }
10885
10886 char *
10887 MakeName (char *template)
10888 {
10889     time_t clock;
10890     struct tm *tm;
10891     static char buf[MSG_SIZ];
10892     char *p = buf;
10893     int i;
10894
10895     clock = time((time_t *)NULL);
10896     tm = localtime(&clock);
10897
10898     while(*p++ = *template++) if(p[-1] == '%') {
10899         switch(*template++) {
10900           case 0:   *p = 0; return buf;
10901           case 'Y': i = tm->tm_year+1900; break;
10902           case 'y': i = tm->tm_year-100; break;
10903           case 'M': i = tm->tm_mon+1; break;
10904           case 'd': i = tm->tm_mday; break;
10905           case 'h': i = tm->tm_hour; break;
10906           case 'm': i = tm->tm_min; break;
10907           case 's': i = tm->tm_sec; break;
10908           default:  i = 0;
10909         }
10910         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10911     }
10912     return buf;
10913 }
10914
10915 int
10916 CountPlayers (char *p)
10917 {
10918     int n = 0;
10919     while(p = strchr(p, '\n')) p++, n++; // count participants
10920     return n;
10921 }
10922
10923 FILE *
10924 WriteTourneyFile (char *results, FILE *f)
10925 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10926     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10927     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10928         // create a file with tournament description
10929         fprintf(f, "-participants {%s}\n", appData.participants);
10930         fprintf(f, "-seedBase %d\n", appData.seedBase);
10931         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10932         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10933         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10934         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10935         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10936         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10937         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10938         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10939         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10940         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10941         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10942         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10943         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10944         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10945         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10946         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10947         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10948         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10949         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10950         fprintf(f, "-smpCores %d\n", appData.smpCores);
10951         if(searchTime > 0)
10952                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10953         else {
10954                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10955                 fprintf(f, "-tc %s\n", appData.timeControl);
10956                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10957         }
10958         fprintf(f, "-results \"%s\"\n", results);
10959     }
10960     return f;
10961 }
10962
10963 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10964
10965 void
10966 Substitute (char *participants, int expunge)
10967 {
10968     int i, changed, changes=0, nPlayers=0;
10969     char *p, *q, *r, buf[MSG_SIZ];
10970     if(participants == NULL) return;
10971     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10972     r = p = participants; q = appData.participants;
10973     while(*p && *p == *q) {
10974         if(*p == '\n') r = p+1, nPlayers++;
10975         p++; q++;
10976     }
10977     if(*p) { // difference
10978         while(*p && *p++ != '\n');
10979         while(*q && *q++ != '\n');
10980       changed = nPlayers;
10981         changes = 1 + (strcmp(p, q) != 0);
10982     }
10983     if(changes == 1) { // a single engine mnemonic was changed
10984         q = r; while(*q) nPlayers += (*q++ == '\n');
10985         p = buf; while(*r && (*p = *r++) != '\n') p++;
10986         *p = NULLCHAR;
10987         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10988         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10989         if(mnemonic[i]) { // The substitute is valid
10990             FILE *f;
10991             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10992                 flock(fileno(f), LOCK_EX);
10993                 ParseArgsFromFile(f);
10994                 fseek(f, 0, SEEK_SET);
10995                 FREE(appData.participants); appData.participants = participants;
10996                 if(expunge) { // erase results of replaced engine
10997                     int len = strlen(appData.results), w, b, dummy;
10998                     for(i=0; i<len; i++) {
10999                         Pairing(i, nPlayers, &w, &b, &dummy);
11000                         if((w == changed || b == changed) && appData.results[i] == '*') {
11001                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11002                             fclose(f);
11003                             return;
11004                         }
11005                     }
11006                     for(i=0; i<len; i++) {
11007                         Pairing(i, nPlayers, &w, &b, &dummy);
11008                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11009                     }
11010                 }
11011                 WriteTourneyFile(appData.results, f);
11012                 fclose(f); // release lock
11013                 return;
11014             }
11015         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11016     }
11017     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11018     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11019     free(participants);
11020     return;
11021 }
11022
11023 int
11024 CheckPlayers (char *participants)
11025 {
11026         int i;
11027         char buf[MSG_SIZ], *p;
11028         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11029         while(p = strchr(participants, '\n')) {
11030             *p = NULLCHAR;
11031             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11032             if(!mnemonic[i]) {
11033                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11034                 *p = '\n';
11035                 DisplayError(buf, 0);
11036                 return 1;
11037             }
11038             *p = '\n';
11039             participants = p + 1;
11040         }
11041         return 0;
11042 }
11043
11044 int
11045 CreateTourney (char *name)
11046 {
11047         FILE *f;
11048         if(matchMode && strcmp(name, appData.tourneyFile)) {
11049              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11050         }
11051         if(name[0] == NULLCHAR) {
11052             if(appData.participants[0])
11053                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11054             return 0;
11055         }
11056         f = fopen(name, "r");
11057         if(f) { // file exists
11058             ASSIGN(appData.tourneyFile, name);
11059             ParseArgsFromFile(f); // parse it
11060         } else {
11061             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11062             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11063                 DisplayError(_("Not enough participants"), 0);
11064                 return 0;
11065             }
11066             if(CheckPlayers(appData.participants)) return 0;
11067             ASSIGN(appData.tourneyFile, name);
11068             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11069             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11070         }
11071         fclose(f);
11072         appData.noChessProgram = FALSE;
11073         appData.clockMode = TRUE;
11074         SetGNUMode();
11075         return 1;
11076 }
11077
11078 int
11079 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11080 {
11081     char buf[MSG_SIZ], *p, *q;
11082     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11083     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11084     skip = !all && group[0]; // if group requested, we start in skip mode
11085     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11086         p = names; q = buf; header = 0;
11087         while(*p && *p != '\n') *q++ = *p++;
11088         *q = 0;
11089         if(*p == '\n') p++;
11090         if(buf[0] == '#') {
11091             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11092             depth++; // we must be entering a new group
11093             if(all) continue; // suppress printing group headers when complete list requested
11094             header = 1;
11095             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11096         }
11097         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11098         if(engineList[i]) free(engineList[i]);
11099         engineList[i] = strdup(buf);
11100         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11101         if(engineMnemonic[i]) free(engineMnemonic[i]);
11102         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11103             strcat(buf, " (");
11104             sscanf(q + 8, "%s", buf + strlen(buf));
11105             strcat(buf, ")");
11106         }
11107         engineMnemonic[i] = strdup(buf);
11108         i++;
11109     }
11110     engineList[i] = engineMnemonic[i] = NULL;
11111     return i;
11112 }
11113
11114 // following implemented as macro to avoid type limitations
11115 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11116
11117 void
11118 SwapEngines (int n)
11119 {   // swap settings for first engine and other engine (so far only some selected options)
11120     int h;
11121     char *p;
11122     if(n == 0) return;
11123     SWAP(directory, p)
11124     SWAP(chessProgram, p)
11125     SWAP(isUCI, h)
11126     SWAP(hasOwnBookUCI, h)
11127     SWAP(protocolVersion, h)
11128     SWAP(reuse, h)
11129     SWAP(scoreIsAbsolute, h)
11130     SWAP(timeOdds, h)
11131     SWAP(logo, p)
11132     SWAP(pgnName, p)
11133     SWAP(pvSAN, h)
11134     SWAP(engOptions, p)
11135     SWAP(engInitString, p)
11136     SWAP(computerString, p)
11137     SWAP(features, p)
11138     SWAP(fenOverride, p)
11139     SWAP(NPS, h)
11140     SWAP(accumulateTC, h)
11141     SWAP(drawDepth, h)
11142     SWAP(host, p)
11143     SWAP(pseudo, h)
11144 }
11145
11146 int
11147 GetEngineLine (char *s, int n)
11148 {
11149     int i;
11150     char buf[MSG_SIZ];
11151     extern char *icsNames;
11152     if(!s || !*s) return 0;
11153     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11154     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11155     if(!mnemonic[i]) return 0;
11156     if(n == 11) return 1; // just testing if there was a match
11157     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11158     if(n == 1) SwapEngines(n);
11159     ParseArgsFromString(buf);
11160     if(n == 1) SwapEngines(n);
11161     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11162         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11163         ParseArgsFromString(buf);
11164     }
11165     return 1;
11166 }
11167
11168 int
11169 SetPlayer (int player, char *p)
11170 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11171     int i;
11172     char buf[MSG_SIZ], *engineName;
11173     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11174     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11175     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11176     if(mnemonic[i]) {
11177         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11178         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11179         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11180         ParseArgsFromString(buf);
11181     } else { // no engine with this nickname is installed!
11182         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11183         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11184         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11185         ModeHighlight();
11186         DisplayError(buf, 0);
11187         return 0;
11188     }
11189     free(engineName);
11190     return i;
11191 }
11192
11193 char *recentEngines;
11194
11195 void
11196 RecentEngineEvent (int nr)
11197 {
11198     int n;
11199 //    SwapEngines(1); // bump first to second
11200 //    ReplaceEngine(&second, 1); // and load it there
11201     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11202     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11203     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11204         ReplaceEngine(&first, 0);
11205         FloatToFront(&appData.recentEngineList, command[n]);
11206     }
11207 }
11208
11209 int
11210 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11211 {   // determine players from game number
11212     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11213
11214     if(appData.tourneyType == 0) {
11215         roundsPerCycle = (nPlayers - 1) | 1;
11216         pairingsPerRound = nPlayers / 2;
11217     } else if(appData.tourneyType > 0) {
11218         roundsPerCycle = nPlayers - appData.tourneyType;
11219         pairingsPerRound = appData.tourneyType;
11220     }
11221     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11222     gamesPerCycle = gamesPerRound * roundsPerCycle;
11223     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11224     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11225     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11226     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11227     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11228     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11229
11230     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11231     if(appData.roundSync) *syncInterval = gamesPerRound;
11232
11233     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11234
11235     if(appData.tourneyType == 0) {
11236         if(curPairing == (nPlayers-1)/2 ) {
11237             *whitePlayer = curRound;
11238             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11239         } else {
11240             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11241             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11242             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11243             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11244         }
11245     } else if(appData.tourneyType > 1) {
11246         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11247         *whitePlayer = curRound + appData.tourneyType;
11248     } else if(appData.tourneyType > 0) {
11249         *whitePlayer = curPairing;
11250         *blackPlayer = curRound + appData.tourneyType;
11251     }
11252
11253     // take care of white/black alternation per round.
11254     // For cycles and games this is already taken care of by default, derived from matchGame!
11255     return curRound & 1;
11256 }
11257
11258 int
11259 NextTourneyGame (int nr, int *swapColors)
11260 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11261     char *p, *q;
11262     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11263     FILE *tf;
11264     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11265     tf = fopen(appData.tourneyFile, "r");
11266     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11267     ParseArgsFromFile(tf); fclose(tf);
11268     InitTimeControls(); // TC might be altered from tourney file
11269
11270     nPlayers = CountPlayers(appData.participants); // count participants
11271     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11272     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11273
11274     if(syncInterval) {
11275         p = q = appData.results;
11276         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11277         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11278             DisplayMessage(_("Waiting for other game(s)"),"");
11279             waitingForGame = TRUE;
11280             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11281             return 0;
11282         }
11283         waitingForGame = FALSE;
11284     }
11285
11286     if(appData.tourneyType < 0) {
11287         if(nr>=0 && !pairingReceived) {
11288             char buf[1<<16];
11289             if(pairing.pr == NoProc) {
11290                 if(!appData.pairingEngine[0]) {
11291                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11292                     return 0;
11293                 }
11294                 StartChessProgram(&pairing); // starts the pairing engine
11295             }
11296             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11297             SendToProgram(buf, &pairing);
11298             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11299             SendToProgram(buf, &pairing);
11300             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11301         }
11302         pairingReceived = 0;                              // ... so we continue here
11303         *swapColors = 0;
11304         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11305         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11306         matchGame = 1; roundNr = nr / syncInterval + 1;
11307     }
11308
11309     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11310
11311     // redefine engines, engine dir, etc.
11312     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11313     if(first.pr == NoProc) {
11314       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11315       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11316     }
11317     if(second.pr == NoProc) {
11318       SwapEngines(1);
11319       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11320       SwapEngines(1);         // and make that valid for second engine by swapping
11321       InitEngine(&second, 1);
11322     }
11323     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11324     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11325     return OK;
11326 }
11327
11328 void
11329 NextMatchGame ()
11330 {   // performs game initialization that does not invoke engines, and then tries to start the game
11331     int res, firstWhite, swapColors = 0;
11332     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11333     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
11334         char buf[MSG_SIZ];
11335         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11336         if(strcmp(buf, currentDebugFile)) { // name has changed
11337             FILE *f = fopen(buf, "w");
11338             if(f) { // if opening the new file failed, just keep using the old one
11339                 ASSIGN(currentDebugFile, buf);
11340                 fclose(debugFP);
11341                 debugFP = f;
11342             }
11343             if(appData.serverFileName) {
11344                 if(serverFP) fclose(serverFP);
11345                 serverFP = fopen(appData.serverFileName, "w");
11346                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11347                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11348             }
11349         }
11350     }
11351     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11352     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11353     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11354     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11355     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11356     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11357     Reset(FALSE, first.pr != NoProc);
11358     res = LoadGameOrPosition(matchGame); // setup game
11359     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11360     if(!res) return; // abort when bad game/pos file
11361     TwoMachinesEvent();
11362 }
11363
11364 void
11365 UserAdjudicationEvent (int result)
11366 {
11367     ChessMove gameResult = GameIsDrawn;
11368
11369     if( result > 0 ) {
11370         gameResult = WhiteWins;
11371     }
11372     else if( result < 0 ) {
11373         gameResult = BlackWins;
11374     }
11375
11376     if( gameMode == TwoMachinesPlay ) {
11377         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11378     }
11379 }
11380
11381
11382 // [HGM] save: calculate checksum of game to make games easily identifiable
11383 int
11384 StringCheckSum (char *s)
11385 {
11386         int i = 0;
11387         if(s==NULL) return 0;
11388         while(*s) i = i*259 + *s++;
11389         return i;
11390 }
11391
11392 int
11393 GameCheckSum ()
11394 {
11395         int i, sum=0;
11396         for(i=backwardMostMove; i<forwardMostMove; i++) {
11397                 sum += pvInfoList[i].depth;
11398                 sum += StringCheckSum(parseList[i]);
11399                 sum += StringCheckSum(commentList[i]);
11400                 sum *= 261;
11401         }
11402         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11403         return sum + StringCheckSum(commentList[i]);
11404 } // end of save patch
11405
11406 void
11407 GameEnds (ChessMove result, char *resultDetails, int whosays)
11408 {
11409     GameMode nextGameMode;
11410     int isIcsGame;
11411     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11412
11413     if(endingGame) return; /* [HGM] crash: forbid recursion */
11414     endingGame = 1;
11415     if(twoBoards) { // [HGM] dual: switch back to one board
11416         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11417         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11418     }
11419     if (appData.debugMode) {
11420       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11421               result, resultDetails ? resultDetails : "(null)", whosays);
11422     }
11423
11424     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11425
11426     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11427
11428     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11429         /* If we are playing on ICS, the server decides when the
11430            game is over, but the engine can offer to draw, claim
11431            a draw, or resign.
11432          */
11433 #if ZIPPY
11434         if (appData.zippyPlay && first.initDone) {
11435             if (result == GameIsDrawn) {
11436                 /* In case draw still needs to be claimed */
11437                 SendToICS(ics_prefix);
11438                 SendToICS("draw\n");
11439             } else if (StrCaseStr(resultDetails, "resign")) {
11440                 SendToICS(ics_prefix);
11441                 SendToICS("resign\n");
11442             }
11443         }
11444 #endif
11445         endingGame = 0; /* [HGM] crash */
11446         return;
11447     }
11448
11449     /* If we're loading the game from a file, stop */
11450     if (whosays == GE_FILE) {
11451       (void) StopLoadGameTimer();
11452       gameFileFP = NULL;
11453     }
11454
11455     /* Cancel draw offers */
11456     first.offeredDraw = second.offeredDraw = 0;
11457
11458     /* If this is an ICS game, only ICS can really say it's done;
11459        if not, anyone can. */
11460     isIcsGame = (gameMode == IcsPlayingWhite ||
11461                  gameMode == IcsPlayingBlack ||
11462                  gameMode == IcsObserving    ||
11463                  gameMode == IcsExamining);
11464
11465     if (!isIcsGame || whosays == GE_ICS) {
11466         /* OK -- not an ICS game, or ICS said it was done */
11467         StopClocks();
11468         if (!isIcsGame && !appData.noChessProgram)
11469           SetUserThinkingEnables();
11470
11471         /* [HGM] if a machine claims the game end we verify this claim */
11472         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11473             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11474                 char claimer;
11475                 ChessMove trueResult = (ChessMove) -1;
11476
11477                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11478                                             first.twoMachinesColor[0] :
11479                                             second.twoMachinesColor[0] ;
11480
11481                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11482                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11483                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11484                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11485                 } else
11486                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11487                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11488                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11489                 } else
11490                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11491                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11492                 }
11493
11494                 // now verify win claims, but not in drop games, as we don't understand those yet
11495                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11496                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11497                     (result == WhiteWins && claimer == 'w' ||
11498                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11499                       if (appData.debugMode) {
11500                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11501                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11502                       }
11503                       if(result != trueResult) {
11504                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11505                               result = claimer == 'w' ? BlackWins : WhiteWins;
11506                               resultDetails = buf;
11507                       }
11508                 } else
11509                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11510                     && (forwardMostMove <= backwardMostMove ||
11511                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11512                         (claimer=='b')==(forwardMostMove&1))
11513                                                                                   ) {
11514                       /* [HGM] verify: draws that were not flagged are false claims */
11515                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11516                       result = claimer == 'w' ? BlackWins : WhiteWins;
11517                       resultDetails = buf;
11518                 }
11519                 /* (Claiming a loss is accepted no questions asked!) */
11520             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11521                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11522                 result = GameUnfinished;
11523                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11524             }
11525             /* [HGM] bare: don't allow bare King to win */
11526             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11527                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11528                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11529                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11530                && result != GameIsDrawn)
11531             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11532                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11533                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11534                         if(p >= 0 && p <= (int)WhiteKing) k++;
11535                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11536                 }
11537                 if (appData.debugMode) {
11538                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11539                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11540                 }
11541                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11542                         result = GameIsDrawn;
11543                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11544                         resultDetails = buf;
11545                 }
11546             }
11547         }
11548
11549
11550         if(serverMoves != NULL && !loadFlag) { char c = '=';
11551             if(result==WhiteWins) c = '+';
11552             if(result==BlackWins) c = '-';
11553             if(resultDetails != NULL)
11554                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11555         }
11556         if (resultDetails != NULL) {
11557             gameInfo.result = result;
11558             gameInfo.resultDetails = StrSave(resultDetails);
11559
11560             /* display last move only if game was not loaded from file */
11561             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11562                 DisplayMove(currentMove - 1);
11563
11564             if (forwardMostMove != 0) {
11565                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11566                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11567                                                                 ) {
11568                     if (*appData.saveGameFile != NULLCHAR) {
11569                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11570                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11571                         else
11572                         SaveGameToFile(appData.saveGameFile, TRUE);
11573                     } else if (appData.autoSaveGames) {
11574                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11575                     }
11576                     if (*appData.savePositionFile != NULLCHAR) {
11577                         SavePositionToFile(appData.savePositionFile);
11578                     }
11579                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11580                 }
11581             }
11582
11583             /* Tell program how game ended in case it is learning */
11584             /* [HGM] Moved this to after saving the PGN, just in case */
11585             /* engine died and we got here through time loss. In that */
11586             /* case we will get a fatal error writing the pipe, which */
11587             /* would otherwise lose us the PGN.                       */
11588             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11589             /* output during GameEnds should never be fatal anymore   */
11590             if (gameMode == MachinePlaysWhite ||
11591                 gameMode == MachinePlaysBlack ||
11592                 gameMode == TwoMachinesPlay ||
11593                 gameMode == IcsPlayingWhite ||
11594                 gameMode == IcsPlayingBlack ||
11595                 gameMode == BeginningOfGame) {
11596                 char buf[MSG_SIZ];
11597                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11598                         resultDetails);
11599                 if (first.pr != NoProc) {
11600                     SendToProgram(buf, &first);
11601                 }
11602                 if (second.pr != NoProc &&
11603                     gameMode == TwoMachinesPlay) {
11604                     SendToProgram(buf, &second);
11605                 }
11606             }
11607         }
11608
11609         if (appData.icsActive) {
11610             if (appData.quietPlay &&
11611                 (gameMode == IcsPlayingWhite ||
11612                  gameMode == IcsPlayingBlack)) {
11613                 SendToICS(ics_prefix);
11614                 SendToICS("set shout 1\n");
11615             }
11616             nextGameMode = IcsIdle;
11617             ics_user_moved = FALSE;
11618             /* clean up premove.  It's ugly when the game has ended and the
11619              * premove highlights are still on the board.
11620              */
11621             if (gotPremove) {
11622               gotPremove = FALSE;
11623               ClearPremoveHighlights();
11624               DrawPosition(FALSE, boards[currentMove]);
11625             }
11626             if (whosays == GE_ICS) {
11627                 switch (result) {
11628                 case WhiteWins:
11629                     if (gameMode == IcsPlayingWhite)
11630                         PlayIcsWinSound();
11631                     else if(gameMode == IcsPlayingBlack)
11632                         PlayIcsLossSound();
11633                     break;
11634                 case BlackWins:
11635                     if (gameMode == IcsPlayingBlack)
11636                         PlayIcsWinSound();
11637                     else if(gameMode == IcsPlayingWhite)
11638                         PlayIcsLossSound();
11639                     break;
11640                 case GameIsDrawn:
11641                     PlayIcsDrawSound();
11642                     break;
11643                 default:
11644                     PlayIcsUnfinishedSound();
11645                 }
11646             }
11647             if(appData.quitNext) { ExitEvent(0); return; }
11648         } else if (gameMode == EditGame ||
11649                    gameMode == PlayFromGameFile ||
11650                    gameMode == AnalyzeMode ||
11651                    gameMode == AnalyzeFile) {
11652             nextGameMode = gameMode;
11653         } else {
11654             nextGameMode = EndOfGame;
11655         }
11656         pausing = FALSE;
11657         ModeHighlight();
11658     } else {
11659         nextGameMode = gameMode;
11660     }
11661
11662     if (appData.noChessProgram) {
11663         gameMode = nextGameMode;
11664         ModeHighlight();
11665         endingGame = 0; /* [HGM] crash */
11666         return;
11667     }
11668
11669     if (first.reuse) {
11670         /* Put first chess program into idle state */
11671         if (first.pr != NoProc &&
11672             (gameMode == MachinePlaysWhite ||
11673              gameMode == MachinePlaysBlack ||
11674              gameMode == TwoMachinesPlay ||
11675              gameMode == IcsPlayingWhite ||
11676              gameMode == IcsPlayingBlack ||
11677              gameMode == BeginningOfGame)) {
11678             SendToProgram("force\n", &first);
11679             if (first.usePing) {
11680               char buf[MSG_SIZ];
11681               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11682               SendToProgram(buf, &first);
11683             }
11684         }
11685     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11686         /* Kill off first chess program */
11687         if (first.isr != NULL)
11688           RemoveInputSource(first.isr);
11689         first.isr = NULL;
11690
11691         if (first.pr != NoProc) {
11692             ExitAnalyzeMode();
11693             DoSleep( appData.delayBeforeQuit );
11694             SendToProgram("quit\n", &first);
11695             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11696             first.reload = TRUE;
11697         }
11698         first.pr = NoProc;
11699     }
11700     if (second.reuse) {
11701         /* Put second chess program into idle state */
11702         if (second.pr != NoProc &&
11703             gameMode == TwoMachinesPlay) {
11704             SendToProgram("force\n", &second);
11705             if (second.usePing) {
11706               char buf[MSG_SIZ];
11707               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11708               SendToProgram(buf, &second);
11709             }
11710         }
11711     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11712         /* Kill off second chess program */
11713         if (second.isr != NULL)
11714           RemoveInputSource(second.isr);
11715         second.isr = NULL;
11716
11717         if (second.pr != NoProc) {
11718             DoSleep( appData.delayBeforeQuit );
11719             SendToProgram("quit\n", &second);
11720             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11721             second.reload = TRUE;
11722         }
11723         second.pr = NoProc;
11724     }
11725
11726     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11727         char resChar = '=';
11728         switch (result) {
11729         case WhiteWins:
11730           resChar = '+';
11731           if (first.twoMachinesColor[0] == 'w') {
11732             first.matchWins++;
11733           } else {
11734             second.matchWins++;
11735           }
11736           break;
11737         case BlackWins:
11738           resChar = '-';
11739           if (first.twoMachinesColor[0] == 'b') {
11740             first.matchWins++;
11741           } else {
11742             second.matchWins++;
11743           }
11744           break;
11745         case GameUnfinished:
11746           resChar = ' ';
11747         default:
11748           break;
11749         }
11750
11751         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11752         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11753             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11754             ReserveGame(nextGame, resChar); // sets nextGame
11755             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11756             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11757         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11758
11759         if (nextGame <= appData.matchGames && !abortMatch) {
11760             gameMode = nextGameMode;
11761             matchGame = nextGame; // this will be overruled in tourney mode!
11762             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11763             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11764             endingGame = 0; /* [HGM] crash */
11765             return;
11766         } else {
11767             gameMode = nextGameMode;
11768             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11769                      first.tidy, second.tidy,
11770                      first.matchWins, second.matchWins,
11771                      appData.matchGames - (first.matchWins + second.matchWins));
11772             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11773             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11774             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11775             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11776                 first.twoMachinesColor = "black\n";
11777                 second.twoMachinesColor = "white\n";
11778             } else {
11779                 first.twoMachinesColor = "white\n";
11780                 second.twoMachinesColor = "black\n";
11781             }
11782         }
11783     }
11784     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11785         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11786       ExitAnalyzeMode();
11787     gameMode = nextGameMode;
11788     ModeHighlight();
11789     endingGame = 0;  /* [HGM] crash */
11790     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11791         if(matchMode == TRUE) { // match through command line: exit with or without popup
11792             if(ranking) {
11793                 ToNrEvent(forwardMostMove);
11794                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11795                 else ExitEvent(0);
11796             } else DisplayFatalError(buf, 0, 0);
11797         } else { // match through menu; just stop, with or without popup
11798             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11799             ModeHighlight();
11800             if(ranking){
11801                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11802             } else DisplayNote(buf);
11803       }
11804       if(ranking) free(ranking);
11805     }
11806 }
11807
11808 /* Assumes program was just initialized (initString sent).
11809    Leaves program in force mode. */
11810 void
11811 FeedMovesToProgram (ChessProgramState *cps, int upto)
11812 {
11813     int i;
11814
11815     if (appData.debugMode)
11816       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11817               startedFromSetupPosition ? "position and " : "",
11818               backwardMostMove, upto, cps->which);
11819     if(currentlyInitializedVariant != gameInfo.variant) {
11820       char buf[MSG_SIZ];
11821         // [HGM] variantswitch: make engine aware of new variant
11822         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11823                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11824                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11825         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11826         SendToProgram(buf, cps);
11827         currentlyInitializedVariant = gameInfo.variant;
11828     }
11829     SendToProgram("force\n", cps);
11830     if (startedFromSetupPosition) {
11831         SendBoard(cps, backwardMostMove);
11832     if (appData.debugMode) {
11833         fprintf(debugFP, "feedMoves\n");
11834     }
11835     }
11836     for (i = backwardMostMove; i < upto; i++) {
11837         SendMoveToProgram(i, cps);
11838     }
11839 }
11840
11841
11842 int
11843 ResurrectChessProgram ()
11844 {
11845      /* The chess program may have exited.
11846         If so, restart it and feed it all the moves made so far. */
11847     static int doInit = 0;
11848
11849     if (appData.noChessProgram) return 1;
11850
11851     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11852         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11853         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11854         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11855     } else {
11856         if (first.pr != NoProc) return 1;
11857         StartChessProgram(&first);
11858     }
11859     InitChessProgram(&first, FALSE);
11860     FeedMovesToProgram(&first, currentMove);
11861
11862     if (!first.sendTime) {
11863         /* can't tell gnuchess what its clock should read,
11864            so we bow to its notion. */
11865         ResetClocks();
11866         timeRemaining[0][currentMove] = whiteTimeRemaining;
11867         timeRemaining[1][currentMove] = blackTimeRemaining;
11868     }
11869
11870     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11871                 appData.icsEngineAnalyze) && first.analysisSupport) {
11872       SendToProgram("analyze\n", &first);
11873       first.analyzing = TRUE;
11874     }
11875     return 1;
11876 }
11877
11878 /*
11879  * Button procedures
11880  */
11881 void
11882 Reset (int redraw, int init)
11883 {
11884     int i;
11885
11886     if (appData.debugMode) {
11887         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11888                 redraw, init, gameMode);
11889     }
11890     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11891     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11892     CleanupTail(); // [HGM] vari: delete any stored variations
11893     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11894     pausing = pauseExamInvalid = FALSE;
11895     startedFromSetupPosition = blackPlaysFirst = FALSE;
11896     firstMove = TRUE;
11897     whiteFlag = blackFlag = FALSE;
11898     userOfferedDraw = FALSE;
11899     hintRequested = bookRequested = FALSE;
11900     first.maybeThinking = FALSE;
11901     second.maybeThinking = FALSE;
11902     first.bookSuspend = FALSE; // [HGM] book
11903     second.bookSuspend = FALSE;
11904     thinkOutput[0] = NULLCHAR;
11905     lastHint[0] = NULLCHAR;
11906     ClearGameInfo(&gameInfo);
11907     gameInfo.variant = StringToVariant(appData.variant);
11908     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11909     ics_user_moved = ics_clock_paused = FALSE;
11910     ics_getting_history = H_FALSE;
11911     ics_gamenum = -1;
11912     white_holding[0] = black_holding[0] = NULLCHAR;
11913     ClearProgramStats();
11914     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11915
11916     ResetFrontEnd();
11917     ClearHighlights();
11918     flipView = appData.flipView;
11919     ClearPremoveHighlights();
11920     gotPremove = FALSE;
11921     alarmSounded = FALSE;
11922     killX = killY = -1; // [HGM] lion
11923
11924     GameEnds(EndOfFile, NULL, GE_PLAYER);
11925     if(appData.serverMovesName != NULL) {
11926         /* [HGM] prepare to make moves file for broadcasting */
11927         clock_t t = clock();
11928         if(serverMoves != NULL) fclose(serverMoves);
11929         serverMoves = fopen(appData.serverMovesName, "r");
11930         if(serverMoves != NULL) {
11931             fclose(serverMoves);
11932             /* delay 15 sec before overwriting, so all clients can see end */
11933             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11934         }
11935         serverMoves = fopen(appData.serverMovesName, "w");
11936     }
11937
11938     ExitAnalyzeMode();
11939     gameMode = BeginningOfGame;
11940     ModeHighlight();
11941     if(appData.icsActive) gameInfo.variant = VariantNormal;
11942     currentMove = forwardMostMove = backwardMostMove = 0;
11943     MarkTargetSquares(1);
11944     InitPosition(redraw);
11945     for (i = 0; i < MAX_MOVES; i++) {
11946         if (commentList[i] != NULL) {
11947             free(commentList[i]);
11948             commentList[i] = NULL;
11949         }
11950     }
11951     ResetClocks();
11952     timeRemaining[0][0] = whiteTimeRemaining;
11953     timeRemaining[1][0] = blackTimeRemaining;
11954
11955     if (first.pr == NoProc) {
11956         StartChessProgram(&first);
11957     }
11958     if (init) {
11959             InitChessProgram(&first, startedFromSetupPosition);
11960     }
11961     DisplayTitle("");
11962     DisplayMessage("", "");
11963     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11964     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11965     ClearMap();        // [HGM] exclude: invalidate map
11966 }
11967
11968 void
11969 AutoPlayGameLoop ()
11970 {
11971     for (;;) {
11972         if (!AutoPlayOneMove())
11973           return;
11974         if (matchMode || appData.timeDelay == 0)
11975           continue;
11976         if (appData.timeDelay < 0)
11977           return;
11978         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11979         break;
11980     }
11981 }
11982
11983 void
11984 AnalyzeNextGame()
11985 {
11986     ReloadGame(1); // next game
11987 }
11988
11989 int
11990 AutoPlayOneMove ()
11991 {
11992     int fromX, fromY, toX, toY;
11993
11994     if (appData.debugMode) {
11995       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11996     }
11997
11998     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11999       return FALSE;
12000
12001     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12002       pvInfoList[currentMove].depth = programStats.depth;
12003       pvInfoList[currentMove].score = programStats.score;
12004       pvInfoList[currentMove].time  = 0;
12005       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12006       else { // append analysis of final position as comment
12007         char buf[MSG_SIZ];
12008         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12009         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12010       }
12011       programStats.depth = 0;
12012     }
12013
12014     if (currentMove >= forwardMostMove) {
12015       if(gameMode == AnalyzeFile) {
12016           if(appData.loadGameIndex == -1) {
12017             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12018           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12019           } else {
12020           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12021         }
12022       }
12023 //      gameMode = EndOfGame;
12024 //      ModeHighlight();
12025
12026       /* [AS] Clear current move marker at the end of a game */
12027       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12028
12029       return FALSE;
12030     }
12031
12032     toX = moveList[currentMove][2] - AAA;
12033     toY = moveList[currentMove][3] - ONE;
12034
12035     if (moveList[currentMove][1] == '@') {
12036         if (appData.highlightLastMove) {
12037             SetHighlights(-1, -1, toX, toY);
12038         }
12039     } else {
12040         int viaX = moveList[currentMove][5] - AAA;
12041         int viaY = moveList[currentMove][6] - ONE;
12042         fromX = moveList[currentMove][0] - AAA;
12043         fromY = moveList[currentMove][1] - ONE;
12044
12045         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12046
12047         if(moveList[currentMove][4] == ';') { // multi-leg
12048             ChessSquare piece = boards[currentMove][viaY][viaX];
12049             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
12050             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
12051             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
12052             boards[currentMove][viaY][viaX] = piece;
12053         } else
12054         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12055
12056         if (appData.highlightLastMove) {
12057             SetHighlights(fromX, fromY, toX, toY);
12058         }
12059     }
12060     DisplayMove(currentMove);
12061     SendMoveToProgram(currentMove++, &first);
12062     DisplayBothClocks();
12063     DrawPosition(FALSE, boards[currentMove]);
12064     // [HGM] PV info: always display, routine tests if empty
12065     DisplayComment(currentMove - 1, commentList[currentMove]);
12066     return TRUE;
12067 }
12068
12069
12070 int
12071 LoadGameOneMove (ChessMove readAhead)
12072 {
12073     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12074     char promoChar = NULLCHAR;
12075     ChessMove moveType;
12076     char move[MSG_SIZ];
12077     char *p, *q;
12078
12079     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12080         gameMode != AnalyzeMode && gameMode != Training) {
12081         gameFileFP = NULL;
12082         return FALSE;
12083     }
12084
12085     yyboardindex = forwardMostMove;
12086     if (readAhead != EndOfFile) {
12087       moveType = readAhead;
12088     } else {
12089       if (gameFileFP == NULL)
12090           return FALSE;
12091       moveType = (ChessMove) Myylex();
12092     }
12093
12094     done = FALSE;
12095     switch (moveType) {
12096       case Comment:
12097         if (appData.debugMode)
12098           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12099         p = yy_text;
12100
12101         /* append the comment but don't display it */
12102         AppendComment(currentMove, p, FALSE);
12103         return TRUE;
12104
12105       case WhiteCapturesEnPassant:
12106       case BlackCapturesEnPassant:
12107       case WhitePromotion:
12108       case BlackPromotion:
12109       case WhiteNonPromotion:
12110       case BlackNonPromotion:
12111       case NormalMove:
12112       case FirstLeg:
12113       case WhiteKingSideCastle:
12114       case WhiteQueenSideCastle:
12115       case BlackKingSideCastle:
12116       case BlackQueenSideCastle:
12117       case WhiteKingSideCastleWild:
12118       case WhiteQueenSideCastleWild:
12119       case BlackKingSideCastleWild:
12120       case BlackQueenSideCastleWild:
12121       /* PUSH Fabien */
12122       case WhiteHSideCastleFR:
12123       case WhiteASideCastleFR:
12124       case BlackHSideCastleFR:
12125       case BlackASideCastleFR:
12126       /* POP Fabien */
12127         if (appData.debugMode)
12128           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12129         fromX = currentMoveString[0] - AAA;
12130         fromY = currentMoveString[1] - ONE;
12131         toX = currentMoveString[2] - AAA;
12132         toY = currentMoveString[3] - ONE;
12133         promoChar = currentMoveString[4];
12134         if(promoChar == ';') promoChar = NULLCHAR;
12135         break;
12136
12137       case WhiteDrop:
12138       case BlackDrop:
12139         if (appData.debugMode)
12140           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12141         fromX = moveType == WhiteDrop ?
12142           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12143         (int) CharToPiece(ToLower(currentMoveString[0]));
12144         fromY = DROP_RANK;
12145         toX = currentMoveString[2] - AAA;
12146         toY = currentMoveString[3] - ONE;
12147         break;
12148
12149       case WhiteWins:
12150       case BlackWins:
12151       case GameIsDrawn:
12152       case GameUnfinished:
12153         if (appData.debugMode)
12154           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12155         p = strchr(yy_text, '{');
12156         if (p == NULL) p = strchr(yy_text, '(');
12157         if (p == NULL) {
12158             p = yy_text;
12159             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12160         } else {
12161             q = strchr(p, *p == '{' ? '}' : ')');
12162             if (q != NULL) *q = NULLCHAR;
12163             p++;
12164         }
12165         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12166         GameEnds(moveType, p, GE_FILE);
12167         done = TRUE;
12168         if (cmailMsgLoaded) {
12169             ClearHighlights();
12170             flipView = WhiteOnMove(currentMove);
12171             if (moveType == GameUnfinished) flipView = !flipView;
12172             if (appData.debugMode)
12173               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12174         }
12175         break;
12176
12177       case EndOfFile:
12178         if (appData.debugMode)
12179           fprintf(debugFP, "Parser hit end of file\n");
12180         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12181           case MT_NONE:
12182           case MT_CHECK:
12183             break;
12184           case MT_CHECKMATE:
12185           case MT_STAINMATE:
12186             if (WhiteOnMove(currentMove)) {
12187                 GameEnds(BlackWins, "Black mates", GE_FILE);
12188             } else {
12189                 GameEnds(WhiteWins, "White mates", GE_FILE);
12190             }
12191             break;
12192           case MT_STALEMATE:
12193             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12194             break;
12195         }
12196         done = TRUE;
12197         break;
12198
12199       case MoveNumberOne:
12200         if (lastLoadGameStart == GNUChessGame) {
12201             /* GNUChessGames have numbers, but they aren't move numbers */
12202             if (appData.debugMode)
12203               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12204                       yy_text, (int) moveType);
12205             return LoadGameOneMove(EndOfFile); /* tail recursion */
12206         }
12207         /* else fall thru */
12208
12209       case XBoardGame:
12210       case GNUChessGame:
12211       case PGNTag:
12212         /* Reached start of next game in file */
12213         if (appData.debugMode)
12214           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12215         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12216           case MT_NONE:
12217           case MT_CHECK:
12218             break;
12219           case MT_CHECKMATE:
12220           case MT_STAINMATE:
12221             if (WhiteOnMove(currentMove)) {
12222                 GameEnds(BlackWins, "Black mates", GE_FILE);
12223             } else {
12224                 GameEnds(WhiteWins, "White mates", GE_FILE);
12225             }
12226             break;
12227           case MT_STALEMATE:
12228             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12229             break;
12230         }
12231         done = TRUE;
12232         break;
12233
12234       case PositionDiagram:     /* should not happen; ignore */
12235       case ElapsedTime:         /* ignore */
12236       case NAG:                 /* ignore */
12237         if (appData.debugMode)
12238           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12239                   yy_text, (int) moveType);
12240         return LoadGameOneMove(EndOfFile); /* tail recursion */
12241
12242       case IllegalMove:
12243         if (appData.testLegality) {
12244             if (appData.debugMode)
12245               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12246             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12247                     (forwardMostMove / 2) + 1,
12248                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12249             DisplayError(move, 0);
12250             done = TRUE;
12251         } else {
12252             if (appData.debugMode)
12253               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12254                       yy_text, currentMoveString);
12255             if(currentMoveString[1] == '@') {
12256                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12257                 fromY = DROP_RANK;
12258             } else {
12259                 fromX = currentMoveString[0] - AAA;
12260                 fromY = currentMoveString[1] - ONE;
12261             }
12262             toX = currentMoveString[2] - AAA;
12263             toY = currentMoveString[3] - ONE;
12264             promoChar = currentMoveString[4];
12265         }
12266         break;
12267
12268       case AmbiguousMove:
12269         if (appData.debugMode)
12270           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12271         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12272                 (forwardMostMove / 2) + 1,
12273                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12274         DisplayError(move, 0);
12275         done = TRUE;
12276         break;
12277
12278       default:
12279       case ImpossibleMove:
12280         if (appData.debugMode)
12281           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12282         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12283                 (forwardMostMove / 2) + 1,
12284                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12285         DisplayError(move, 0);
12286         done = TRUE;
12287         break;
12288     }
12289
12290     if (done) {
12291         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12292             DrawPosition(FALSE, boards[currentMove]);
12293             DisplayBothClocks();
12294             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12295               DisplayComment(currentMove - 1, commentList[currentMove]);
12296         }
12297         (void) StopLoadGameTimer();
12298         gameFileFP = NULL;
12299         cmailOldMove = forwardMostMove;
12300         return FALSE;
12301     } else {
12302         /* currentMoveString is set as a side-effect of yylex */
12303
12304         thinkOutput[0] = NULLCHAR;
12305         MakeMove(fromX, fromY, toX, toY, promoChar);
12306         killX = killY = -1; // [HGM] lion: used up
12307         currentMove = forwardMostMove;
12308         return TRUE;
12309     }
12310 }
12311
12312 /* Load the nth game from the given file */
12313 int
12314 LoadGameFromFile (char *filename, int n, char *title, int useList)
12315 {
12316     FILE *f;
12317     char buf[MSG_SIZ];
12318
12319     if (strcmp(filename, "-") == 0) {
12320         f = stdin;
12321         title = "stdin";
12322     } else {
12323         f = fopen(filename, "rb");
12324         if (f == NULL) {
12325           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12326             DisplayError(buf, errno);
12327             return FALSE;
12328         }
12329     }
12330     if (fseek(f, 0, 0) == -1) {
12331         /* f is not seekable; probably a pipe */
12332         useList = FALSE;
12333     }
12334     if (useList && n == 0) {
12335         int error = GameListBuild(f);
12336         if (error) {
12337             DisplayError(_("Cannot build game list"), error);
12338         } else if (!ListEmpty(&gameList) &&
12339                    ((ListGame *) gameList.tailPred)->number > 1) {
12340             GameListPopUp(f, title);
12341             return TRUE;
12342         }
12343         GameListDestroy();
12344         n = 1;
12345     }
12346     if (n == 0) n = 1;
12347     return LoadGame(f, n, title, FALSE);
12348 }
12349
12350
12351 void
12352 MakeRegisteredMove ()
12353 {
12354     int fromX, fromY, toX, toY;
12355     char promoChar;
12356     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12357         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12358           case CMAIL_MOVE:
12359           case CMAIL_DRAW:
12360             if (appData.debugMode)
12361               fprintf(debugFP, "Restoring %s for game %d\n",
12362                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12363
12364             thinkOutput[0] = NULLCHAR;
12365             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12366             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12367             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12368             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12369             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12370             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12371             MakeMove(fromX, fromY, toX, toY, promoChar);
12372             ShowMove(fromX, fromY, toX, toY);
12373
12374             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12375               case MT_NONE:
12376               case MT_CHECK:
12377                 break;
12378
12379               case MT_CHECKMATE:
12380               case MT_STAINMATE:
12381                 if (WhiteOnMove(currentMove)) {
12382                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12383                 } else {
12384                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12385                 }
12386                 break;
12387
12388               case MT_STALEMATE:
12389                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12390                 break;
12391             }
12392
12393             break;
12394
12395           case CMAIL_RESIGN:
12396             if (WhiteOnMove(currentMove)) {
12397                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12398             } else {
12399                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12400             }
12401             break;
12402
12403           case CMAIL_ACCEPT:
12404             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12405             break;
12406
12407           default:
12408             break;
12409         }
12410     }
12411
12412     return;
12413 }
12414
12415 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12416 int
12417 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12418 {
12419     int retVal;
12420
12421     if (gameNumber > nCmailGames) {
12422         DisplayError(_("No more games in this message"), 0);
12423         return FALSE;
12424     }
12425     if (f == lastLoadGameFP) {
12426         int offset = gameNumber - lastLoadGameNumber;
12427         if (offset == 0) {
12428             cmailMsg[0] = NULLCHAR;
12429             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12430                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12431                 nCmailMovesRegistered--;
12432             }
12433             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12434             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12435                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12436             }
12437         } else {
12438             if (! RegisterMove()) return FALSE;
12439         }
12440     }
12441
12442     retVal = LoadGame(f, gameNumber, title, useList);
12443
12444     /* Make move registered during previous look at this game, if any */
12445     MakeRegisteredMove();
12446
12447     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12448         commentList[currentMove]
12449           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12450         DisplayComment(currentMove - 1, commentList[currentMove]);
12451     }
12452
12453     return retVal;
12454 }
12455
12456 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12457 int
12458 ReloadGame (int offset)
12459 {
12460     int gameNumber = lastLoadGameNumber + offset;
12461     if (lastLoadGameFP == NULL) {
12462         DisplayError(_("No game has been loaded yet"), 0);
12463         return FALSE;
12464     }
12465     if (gameNumber <= 0) {
12466         DisplayError(_("Can't back up any further"), 0);
12467         return FALSE;
12468     }
12469     if (cmailMsgLoaded) {
12470         return CmailLoadGame(lastLoadGameFP, gameNumber,
12471                              lastLoadGameTitle, lastLoadGameUseList);
12472     } else {
12473         return LoadGame(lastLoadGameFP, gameNumber,
12474                         lastLoadGameTitle, lastLoadGameUseList);
12475     }
12476 }
12477
12478 int keys[EmptySquare+1];
12479
12480 int
12481 PositionMatches (Board b1, Board b2)
12482 {
12483     int r, f, sum=0;
12484     switch(appData.searchMode) {
12485         case 1: return CompareWithRights(b1, b2);
12486         case 2:
12487             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12488                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12489             }
12490             return TRUE;
12491         case 3:
12492             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12493               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12494                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12495             }
12496             return sum==0;
12497         case 4:
12498             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12499                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12500             }
12501             return sum==0;
12502     }
12503     return TRUE;
12504 }
12505
12506 #define Q_PROMO  4
12507 #define Q_EP     3
12508 #define Q_BCASTL 2
12509 #define Q_WCASTL 1
12510
12511 int pieceList[256], quickBoard[256];
12512 ChessSquare pieceType[256] = { EmptySquare };
12513 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12514 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12515 int soughtTotal, turn;
12516 Boolean epOK, flipSearch;
12517
12518 typedef struct {
12519     unsigned char piece, to;
12520 } Move;
12521
12522 #define DSIZE (250000)
12523
12524 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12525 Move *moveDatabase = initialSpace;
12526 unsigned int movePtr, dataSize = DSIZE;
12527
12528 int
12529 MakePieceList (Board board, int *counts)
12530 {
12531     int r, f, n=Q_PROMO, total=0;
12532     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12533     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12534         int sq = f + (r<<4);
12535         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12536             quickBoard[sq] = ++n;
12537             pieceList[n] = sq;
12538             pieceType[n] = board[r][f];
12539             counts[board[r][f]]++;
12540             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12541             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12542             total++;
12543         }
12544     }
12545     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12546     return total;
12547 }
12548
12549 void
12550 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12551 {
12552     int sq = fromX + (fromY<<4);
12553     int piece = quickBoard[sq], rook;
12554     quickBoard[sq] = 0;
12555     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12556     if(piece == pieceList[1] && fromY == toY) {
12557       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12558         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12559         moveDatabase[movePtr++].piece = Q_WCASTL;
12560         quickBoard[sq] = piece;
12561         piece = quickBoard[from]; quickBoard[from] = 0;
12562         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12563       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12564         quickBoard[sq] = 0; // remove Rook
12565         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12566         moveDatabase[movePtr++].piece = Q_WCASTL;
12567         quickBoard[sq] = pieceList[1]; // put King
12568         piece = rook;
12569         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12570       }
12571     } else
12572     if(piece == pieceList[2] && fromY == toY) {
12573       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12574         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12575         moveDatabase[movePtr++].piece = Q_BCASTL;
12576         quickBoard[sq] = piece;
12577         piece = quickBoard[from]; quickBoard[from] = 0;
12578         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12579       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12580         quickBoard[sq] = 0; // remove Rook
12581         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12582         moveDatabase[movePtr++].piece = Q_BCASTL;
12583         quickBoard[sq] = pieceList[2]; // put King
12584         piece = rook;
12585         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12586       }
12587     } else
12588     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12589         quickBoard[(fromY<<4)+toX] = 0;
12590         moveDatabase[movePtr].piece = Q_EP;
12591         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12592         moveDatabase[movePtr].to = sq;
12593     } else
12594     if(promoPiece != pieceType[piece]) {
12595         moveDatabase[movePtr++].piece = Q_PROMO;
12596         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12597     }
12598     moveDatabase[movePtr].piece = piece;
12599     quickBoard[sq] = piece;
12600     movePtr++;
12601 }
12602
12603 int
12604 PackGame (Board board)
12605 {
12606     Move *newSpace = NULL;
12607     moveDatabase[movePtr].piece = 0; // terminate previous game
12608     if(movePtr > dataSize) {
12609         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12610         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12611         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12612         if(newSpace) {
12613             int i;
12614             Move *p = moveDatabase, *q = newSpace;
12615             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12616             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12617             moveDatabase = newSpace;
12618         } else { // calloc failed, we must be out of memory. Too bad...
12619             dataSize = 0; // prevent calloc events for all subsequent games
12620             return 0;     // and signal this one isn't cached
12621         }
12622     }
12623     movePtr++;
12624     MakePieceList(board, counts);
12625     return movePtr;
12626 }
12627
12628 int
12629 QuickCompare (Board board, int *minCounts, int *maxCounts)
12630 {   // compare according to search mode
12631     int r, f;
12632     switch(appData.searchMode)
12633     {
12634       case 1: // exact position match
12635         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12636         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12637             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12638         }
12639         break;
12640       case 2: // can have extra material on empty squares
12641         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12642             if(board[r][f] == EmptySquare) continue;
12643             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12644         }
12645         break;
12646       case 3: // material with exact Pawn structure
12647         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12648             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12649             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12650         } // fall through to material comparison
12651       case 4: // exact material
12652         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12653         break;
12654       case 6: // material range with given imbalance
12655         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12656         // fall through to range comparison
12657       case 5: // material range
12658         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12659     }
12660     return TRUE;
12661 }
12662
12663 int
12664 QuickScan (Board board, Move *move)
12665 {   // reconstruct game,and compare all positions in it
12666     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12667     do {
12668         int piece = move->piece;
12669         int to = move->to, from = pieceList[piece];
12670         if(found < 0) { // if already found just scan to game end for final piece count
12671           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12672            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12673            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12674                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12675             ) {
12676             static int lastCounts[EmptySquare+1];
12677             int i;
12678             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12679             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12680           } else stretch = 0;
12681           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12682           if(found >= 0 && !appData.minPieces) return found;
12683         }
12684         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12685           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12686           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12687             piece = (++move)->piece;
12688             from = pieceList[piece];
12689             counts[pieceType[piece]]--;
12690             pieceType[piece] = (ChessSquare) move->to;
12691             counts[move->to]++;
12692           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12693             counts[pieceType[quickBoard[to]]]--;
12694             quickBoard[to] = 0; total--;
12695             move++;
12696             continue;
12697           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12698             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12699             from  = pieceList[piece]; // so this must be King
12700             quickBoard[from] = 0;
12701             pieceList[piece] = to;
12702             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12703             quickBoard[from] = 0; // rook
12704             quickBoard[to] = piece;
12705             to = move->to; piece = move->piece;
12706             goto aftercastle;
12707           }
12708         }
12709         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12710         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12711         quickBoard[from] = 0;
12712       aftercastle:
12713         quickBoard[to] = piece;
12714         pieceList[piece] = to;
12715         cnt++; turn ^= 3;
12716         move++;
12717     } while(1);
12718 }
12719
12720 void
12721 InitSearch ()
12722 {
12723     int r, f;
12724     flipSearch = FALSE;
12725     CopyBoard(soughtBoard, boards[currentMove]);
12726     soughtTotal = MakePieceList(soughtBoard, maxSought);
12727     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12728     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12729     CopyBoard(reverseBoard, boards[currentMove]);
12730     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12731         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12732         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12733         reverseBoard[r][f] = piece;
12734     }
12735     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12736     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12737     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12738                  || (boards[currentMove][CASTLING][2] == NoRights ||
12739                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12740                  && (boards[currentMove][CASTLING][5] == NoRights ||
12741                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12742       ) {
12743         flipSearch = TRUE;
12744         CopyBoard(flipBoard, soughtBoard);
12745         CopyBoard(rotateBoard, reverseBoard);
12746         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12747             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12748             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12749         }
12750     }
12751     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12752     if(appData.searchMode >= 5) {
12753         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12754         MakePieceList(soughtBoard, minSought);
12755         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12756     }
12757     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12758         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12759 }
12760
12761 GameInfo dummyInfo;
12762 static int creatingBook;
12763
12764 int
12765 GameContainsPosition (FILE *f, ListGame *lg)
12766 {
12767     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12768     int fromX, fromY, toX, toY;
12769     char promoChar;
12770     static int initDone=FALSE;
12771
12772     // weed out games based on numerical tag comparison
12773     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12774     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12775     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12776     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12777     if(!initDone) {
12778         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12779         initDone = TRUE;
12780     }
12781     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12782     else CopyBoard(boards[scratch], initialPosition); // default start position
12783     if(lg->moves) {
12784         turn = btm + 1;
12785         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12786         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12787     }
12788     if(btm) plyNr++;
12789     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12790     fseek(f, lg->offset, 0);
12791     yynewfile(f);
12792     while(1) {
12793         yyboardindex = scratch;
12794         quickFlag = plyNr+1;
12795         next = Myylex();
12796         quickFlag = 0;
12797         switch(next) {
12798             case PGNTag:
12799                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12800             default:
12801                 continue;
12802
12803             case XBoardGame:
12804             case GNUChessGame:
12805                 if(plyNr) return -1; // after we have seen moves, this is for new game
12806               continue;
12807
12808             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12809             case ImpossibleMove:
12810             case WhiteWins: // game ends here with these four
12811             case BlackWins:
12812             case GameIsDrawn:
12813             case GameUnfinished:
12814                 return -1;
12815
12816             case IllegalMove:
12817                 if(appData.testLegality) return -1;
12818             case WhiteCapturesEnPassant:
12819             case BlackCapturesEnPassant:
12820             case WhitePromotion:
12821             case BlackPromotion:
12822             case WhiteNonPromotion:
12823             case BlackNonPromotion:
12824             case NormalMove:
12825             case FirstLeg:
12826             case WhiteKingSideCastle:
12827             case WhiteQueenSideCastle:
12828             case BlackKingSideCastle:
12829             case BlackQueenSideCastle:
12830             case WhiteKingSideCastleWild:
12831             case WhiteQueenSideCastleWild:
12832             case BlackKingSideCastleWild:
12833             case BlackQueenSideCastleWild:
12834             case WhiteHSideCastleFR:
12835             case WhiteASideCastleFR:
12836             case BlackHSideCastleFR:
12837             case BlackASideCastleFR:
12838                 fromX = currentMoveString[0] - AAA;
12839                 fromY = currentMoveString[1] - ONE;
12840                 toX = currentMoveString[2] - AAA;
12841                 toY = currentMoveString[3] - ONE;
12842                 promoChar = currentMoveString[4];
12843                 break;
12844             case WhiteDrop:
12845             case BlackDrop:
12846                 fromX = next == WhiteDrop ?
12847                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12848                   (int) CharToPiece(ToLower(currentMoveString[0]));
12849                 fromY = DROP_RANK;
12850                 toX = currentMoveString[2] - AAA;
12851                 toY = currentMoveString[3] - ONE;
12852                 promoChar = 0;
12853                 break;
12854         }
12855         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12856         plyNr++;
12857         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12858         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12859         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12860         if(appData.findMirror) {
12861             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12862             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12863         }
12864     }
12865 }
12866
12867 /* Load the nth game from open file f */
12868 int
12869 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12870 {
12871     ChessMove cm;
12872     char buf[MSG_SIZ];
12873     int gn = gameNumber;
12874     ListGame *lg = NULL;
12875     int numPGNTags = 0;
12876     int err, pos = -1;
12877     GameMode oldGameMode;
12878     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12879     char oldName[MSG_SIZ];
12880
12881     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12882
12883     if (appData.debugMode)
12884         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12885
12886     if (gameMode == Training )
12887         SetTrainingModeOff();
12888
12889     oldGameMode = gameMode;
12890     if (gameMode != BeginningOfGame) {
12891       Reset(FALSE, TRUE);
12892     }
12893     killX = killY = -1; // [HGM] lion: in case we did not Reset
12894
12895     gameFileFP = f;
12896     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12897         fclose(lastLoadGameFP);
12898     }
12899
12900     if (useList) {
12901         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12902
12903         if (lg) {
12904             fseek(f, lg->offset, 0);
12905             GameListHighlight(gameNumber);
12906             pos = lg->position;
12907             gn = 1;
12908         }
12909         else {
12910             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12911               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12912             else
12913             DisplayError(_("Game number out of range"), 0);
12914             return FALSE;
12915         }
12916     } else {
12917         GameListDestroy();
12918         if (fseek(f, 0, 0) == -1) {
12919             if (f == lastLoadGameFP ?
12920                 gameNumber == lastLoadGameNumber + 1 :
12921                 gameNumber == 1) {
12922                 gn = 1;
12923             } else {
12924                 DisplayError(_("Can't seek on game file"), 0);
12925                 return FALSE;
12926             }
12927         }
12928     }
12929     lastLoadGameFP = f;
12930     lastLoadGameNumber = gameNumber;
12931     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12932     lastLoadGameUseList = useList;
12933
12934     yynewfile(f);
12935
12936     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12937       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12938                 lg->gameInfo.black);
12939             DisplayTitle(buf);
12940     } else if (*title != NULLCHAR) {
12941         if (gameNumber > 1) {
12942           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12943             DisplayTitle(buf);
12944         } else {
12945             DisplayTitle(title);
12946         }
12947     }
12948
12949     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12950         gameMode = PlayFromGameFile;
12951         ModeHighlight();
12952     }
12953
12954     currentMove = forwardMostMove = backwardMostMove = 0;
12955     CopyBoard(boards[0], initialPosition);
12956     StopClocks();
12957
12958     /*
12959      * Skip the first gn-1 games in the file.
12960      * Also skip over anything that precedes an identifiable
12961      * start of game marker, to avoid being confused by
12962      * garbage at the start of the file.  Currently
12963      * recognized start of game markers are the move number "1",
12964      * the pattern "gnuchess .* game", the pattern
12965      * "^[#;%] [^ ]* game file", and a PGN tag block.
12966      * A game that starts with one of the latter two patterns
12967      * will also have a move number 1, possibly
12968      * following a position diagram.
12969      * 5-4-02: Let's try being more lenient and allowing a game to
12970      * start with an unnumbered move.  Does that break anything?
12971      */
12972     cm = lastLoadGameStart = EndOfFile;
12973     while (gn > 0) {
12974         yyboardindex = forwardMostMove;
12975         cm = (ChessMove) Myylex();
12976         switch (cm) {
12977           case EndOfFile:
12978             if (cmailMsgLoaded) {
12979                 nCmailGames = CMAIL_MAX_GAMES - gn;
12980             } else {
12981                 Reset(TRUE, TRUE);
12982                 DisplayError(_("Game not found in file"), 0);
12983             }
12984             return FALSE;
12985
12986           case GNUChessGame:
12987           case XBoardGame:
12988             gn--;
12989             lastLoadGameStart = cm;
12990             break;
12991
12992           case MoveNumberOne:
12993             switch (lastLoadGameStart) {
12994               case GNUChessGame:
12995               case XBoardGame:
12996               case PGNTag:
12997                 break;
12998               case MoveNumberOne:
12999               case EndOfFile:
13000                 gn--;           /* count this game */
13001                 lastLoadGameStart = cm;
13002                 break;
13003               default:
13004                 /* impossible */
13005                 break;
13006             }
13007             break;
13008
13009           case PGNTag:
13010             switch (lastLoadGameStart) {
13011               case GNUChessGame:
13012               case PGNTag:
13013               case MoveNumberOne:
13014               case EndOfFile:
13015                 gn--;           /* count this game */
13016                 lastLoadGameStart = cm;
13017                 break;
13018               case XBoardGame:
13019                 lastLoadGameStart = cm; /* game counted already */
13020                 break;
13021               default:
13022                 /* impossible */
13023                 break;
13024             }
13025             if (gn > 0) {
13026                 do {
13027                     yyboardindex = forwardMostMove;
13028                     cm = (ChessMove) Myylex();
13029                 } while (cm == PGNTag || cm == Comment);
13030             }
13031             break;
13032
13033           case WhiteWins:
13034           case BlackWins:
13035           case GameIsDrawn:
13036             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13037                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13038                     != CMAIL_OLD_RESULT) {
13039                     nCmailResults ++ ;
13040                     cmailResult[  CMAIL_MAX_GAMES
13041                                 - gn - 1] = CMAIL_OLD_RESULT;
13042                 }
13043             }
13044             break;
13045
13046           case NormalMove:
13047           case FirstLeg:
13048             /* Only a NormalMove can be at the start of a game
13049              * without a position diagram. */
13050             if (lastLoadGameStart == EndOfFile ) {
13051               gn--;
13052               lastLoadGameStart = MoveNumberOne;
13053             }
13054             break;
13055
13056           default:
13057             break;
13058         }
13059     }
13060
13061     if (appData.debugMode)
13062       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13063
13064     if (cm == XBoardGame) {
13065         /* Skip any header junk before position diagram and/or move 1 */
13066         for (;;) {
13067             yyboardindex = forwardMostMove;
13068             cm = (ChessMove) Myylex();
13069
13070             if (cm == EndOfFile ||
13071                 cm == GNUChessGame || cm == XBoardGame) {
13072                 /* Empty game; pretend end-of-file and handle later */
13073                 cm = EndOfFile;
13074                 break;
13075             }
13076
13077             if (cm == MoveNumberOne || cm == PositionDiagram ||
13078                 cm == PGNTag || cm == Comment)
13079               break;
13080         }
13081     } else if (cm == GNUChessGame) {
13082         if (gameInfo.event != NULL) {
13083             free(gameInfo.event);
13084         }
13085         gameInfo.event = StrSave(yy_text);
13086     }
13087
13088     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13089     while (cm == PGNTag) {
13090         if (appData.debugMode)
13091           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13092         err = ParsePGNTag(yy_text, &gameInfo);
13093         if (!err) numPGNTags++;
13094
13095         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13096         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13097             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13098             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13099             InitPosition(TRUE);
13100             oldVariant = gameInfo.variant;
13101             if (appData.debugMode)
13102               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13103         }
13104
13105
13106         if (gameInfo.fen != NULL) {
13107           Board initial_position;
13108           startedFromSetupPosition = TRUE;
13109           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13110             Reset(TRUE, TRUE);
13111             DisplayError(_("Bad FEN position in file"), 0);
13112             return FALSE;
13113           }
13114           CopyBoard(boards[0], initial_position);
13115           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13116             CopyBoard(initialPosition, initial_position);
13117           if (blackPlaysFirst) {
13118             currentMove = forwardMostMove = backwardMostMove = 1;
13119             CopyBoard(boards[1], initial_position);
13120             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13121             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13122             timeRemaining[0][1] = whiteTimeRemaining;
13123             timeRemaining[1][1] = blackTimeRemaining;
13124             if (commentList[0] != NULL) {
13125               commentList[1] = commentList[0];
13126               commentList[0] = NULL;
13127             }
13128           } else {
13129             currentMove = forwardMostMove = backwardMostMove = 0;
13130           }
13131           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13132           {   int i;
13133               initialRulePlies = FENrulePlies;
13134               for( i=0; i< nrCastlingRights; i++ )
13135                   initialRights[i] = initial_position[CASTLING][i];
13136           }
13137           yyboardindex = forwardMostMove;
13138           free(gameInfo.fen);
13139           gameInfo.fen = NULL;
13140         }
13141
13142         yyboardindex = forwardMostMove;
13143         cm = (ChessMove) Myylex();
13144
13145         /* Handle comments interspersed among the tags */
13146         while (cm == Comment) {
13147             char *p;
13148             if (appData.debugMode)
13149               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13150             p = yy_text;
13151             AppendComment(currentMove, p, FALSE);
13152             yyboardindex = forwardMostMove;
13153             cm = (ChessMove) Myylex();
13154         }
13155     }
13156
13157     /* don't rely on existence of Event tag since if game was
13158      * pasted from clipboard the Event tag may not exist
13159      */
13160     if (numPGNTags > 0){
13161         char *tags;
13162         if (gameInfo.variant == VariantNormal) {
13163           VariantClass v = StringToVariant(gameInfo.event);
13164           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13165           if(v < VariantShogi) gameInfo.variant = v;
13166         }
13167         if (!matchMode) {
13168           if( appData.autoDisplayTags ) {
13169             tags = PGNTags(&gameInfo);
13170             TagsPopUp(tags, CmailMsg());
13171             free(tags);
13172           }
13173         }
13174     } else {
13175         /* Make something up, but don't display it now */
13176         SetGameInfo();
13177         TagsPopDown();
13178     }
13179
13180     if (cm == PositionDiagram) {
13181         int i, j;
13182         char *p;
13183         Board initial_position;
13184
13185         if (appData.debugMode)
13186           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13187
13188         if (!startedFromSetupPosition) {
13189             p = yy_text;
13190             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13191               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13192                 switch (*p) {
13193                   case '{':
13194                   case '[':
13195                   case '-':
13196                   case ' ':
13197                   case '\t':
13198                   case '\n':
13199                   case '\r':
13200                     break;
13201                   default:
13202                     initial_position[i][j++] = CharToPiece(*p);
13203                     break;
13204                 }
13205             while (*p == ' ' || *p == '\t' ||
13206                    *p == '\n' || *p == '\r') p++;
13207
13208             if (strncmp(p, "black", strlen("black"))==0)
13209               blackPlaysFirst = TRUE;
13210             else
13211               blackPlaysFirst = FALSE;
13212             startedFromSetupPosition = TRUE;
13213
13214             CopyBoard(boards[0], initial_position);
13215             if (blackPlaysFirst) {
13216                 currentMove = forwardMostMove = backwardMostMove = 1;
13217                 CopyBoard(boards[1], initial_position);
13218                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13219                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13220                 timeRemaining[0][1] = whiteTimeRemaining;
13221                 timeRemaining[1][1] = blackTimeRemaining;
13222                 if (commentList[0] != NULL) {
13223                     commentList[1] = commentList[0];
13224                     commentList[0] = NULL;
13225                 }
13226             } else {
13227                 currentMove = forwardMostMove = backwardMostMove = 0;
13228             }
13229         }
13230         yyboardindex = forwardMostMove;
13231         cm = (ChessMove) Myylex();
13232     }
13233
13234   if(!creatingBook) {
13235     if (first.pr == NoProc) {
13236         StartChessProgram(&first);
13237     }
13238     InitChessProgram(&first, FALSE);
13239     if(gameInfo.variant == VariantUnknown && *oldName) {
13240         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13241         gameInfo.variant = v;
13242     }
13243     SendToProgram("force\n", &first);
13244     if (startedFromSetupPosition) {
13245         SendBoard(&first, forwardMostMove);
13246     if (appData.debugMode) {
13247         fprintf(debugFP, "Load Game\n");
13248     }
13249         DisplayBothClocks();
13250     }
13251   }
13252
13253     /* [HGM] server: flag to write setup moves in broadcast file as one */
13254     loadFlag = appData.suppressLoadMoves;
13255
13256     while (cm == Comment) {
13257         char *p;
13258         if (appData.debugMode)
13259           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13260         p = yy_text;
13261         AppendComment(currentMove, p, FALSE);
13262         yyboardindex = forwardMostMove;
13263         cm = (ChessMove) Myylex();
13264     }
13265
13266     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13267         cm == WhiteWins || cm == BlackWins ||
13268         cm == GameIsDrawn || cm == GameUnfinished) {
13269         DisplayMessage("", _("No moves in game"));
13270         if (cmailMsgLoaded) {
13271             if (appData.debugMode)
13272               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13273             ClearHighlights();
13274             flipView = FALSE;
13275         }
13276         DrawPosition(FALSE, boards[currentMove]);
13277         DisplayBothClocks();
13278         gameMode = EditGame;
13279         ModeHighlight();
13280         gameFileFP = NULL;
13281         cmailOldMove = 0;
13282         return TRUE;
13283     }
13284
13285     // [HGM] PV info: routine tests if comment empty
13286     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13287         DisplayComment(currentMove - 1, commentList[currentMove]);
13288     }
13289     if (!matchMode && appData.timeDelay != 0)
13290       DrawPosition(FALSE, boards[currentMove]);
13291
13292     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13293       programStats.ok_to_send = 1;
13294     }
13295
13296     /* if the first token after the PGN tags is a move
13297      * and not move number 1, retrieve it from the parser
13298      */
13299     if (cm != MoveNumberOne)
13300         LoadGameOneMove(cm);
13301
13302     /* load the remaining moves from the file */
13303     while (LoadGameOneMove(EndOfFile)) {
13304       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13305       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13306     }
13307
13308     /* rewind to the start of the game */
13309     currentMove = backwardMostMove;
13310
13311     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13312
13313     if (oldGameMode == AnalyzeFile) {
13314       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13315       AnalyzeFileEvent();
13316     } else
13317     if (oldGameMode == AnalyzeMode) {
13318       AnalyzeFileEvent();
13319     }
13320
13321     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13322         long int w, b; // [HGM] adjourn: restore saved clock times
13323         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13324         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13325             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13326             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13327         }
13328     }
13329
13330     if(creatingBook) return TRUE;
13331     if (!matchMode && pos > 0) {
13332         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13333     } else
13334     if (matchMode || appData.timeDelay == 0) {
13335       ToEndEvent();
13336     } else if (appData.timeDelay > 0) {
13337       AutoPlayGameLoop();
13338     }
13339
13340     if (appData.debugMode)
13341         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13342
13343     loadFlag = 0; /* [HGM] true game starts */
13344     return TRUE;
13345 }
13346
13347 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13348 int
13349 ReloadPosition (int offset)
13350 {
13351     int positionNumber = lastLoadPositionNumber + offset;
13352     if (lastLoadPositionFP == NULL) {
13353         DisplayError(_("No position has been loaded yet"), 0);
13354         return FALSE;
13355     }
13356     if (positionNumber <= 0) {
13357         DisplayError(_("Can't back up any further"), 0);
13358         return FALSE;
13359     }
13360     return LoadPosition(lastLoadPositionFP, positionNumber,
13361                         lastLoadPositionTitle);
13362 }
13363
13364 /* Load the nth position from the given file */
13365 int
13366 LoadPositionFromFile (char *filename, int n, char *title)
13367 {
13368     FILE *f;
13369     char buf[MSG_SIZ];
13370
13371     if (strcmp(filename, "-") == 0) {
13372         return LoadPosition(stdin, n, "stdin");
13373     } else {
13374         f = fopen(filename, "rb");
13375         if (f == NULL) {
13376             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13377             DisplayError(buf, errno);
13378             return FALSE;
13379         } else {
13380             return LoadPosition(f, n, title);
13381         }
13382     }
13383 }
13384
13385 /* Load the nth position from the given open file, and close it */
13386 int
13387 LoadPosition (FILE *f, int positionNumber, char *title)
13388 {
13389     char *p, line[MSG_SIZ];
13390     Board initial_position;
13391     int i, j, fenMode, pn;
13392
13393     if (gameMode == Training )
13394         SetTrainingModeOff();
13395
13396     if (gameMode != BeginningOfGame) {
13397         Reset(FALSE, TRUE);
13398     }
13399     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13400         fclose(lastLoadPositionFP);
13401     }
13402     if (positionNumber == 0) positionNumber = 1;
13403     lastLoadPositionFP = f;
13404     lastLoadPositionNumber = positionNumber;
13405     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13406     if (first.pr == NoProc && !appData.noChessProgram) {
13407       StartChessProgram(&first);
13408       InitChessProgram(&first, FALSE);
13409     }
13410     pn = positionNumber;
13411     if (positionNumber < 0) {
13412         /* Negative position number means to seek to that byte offset */
13413         if (fseek(f, -positionNumber, 0) == -1) {
13414             DisplayError(_("Can't seek on position file"), 0);
13415             return FALSE;
13416         };
13417         pn = 1;
13418     } else {
13419         if (fseek(f, 0, 0) == -1) {
13420             if (f == lastLoadPositionFP ?
13421                 positionNumber == lastLoadPositionNumber + 1 :
13422                 positionNumber == 1) {
13423                 pn = 1;
13424             } else {
13425                 DisplayError(_("Can't seek on position file"), 0);
13426                 return FALSE;
13427             }
13428         }
13429     }
13430     /* See if this file is FEN or old-style xboard */
13431     if (fgets(line, MSG_SIZ, f) == NULL) {
13432         DisplayError(_("Position not found in file"), 0);
13433         return FALSE;
13434     }
13435     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13436     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13437
13438     if (pn >= 2) {
13439         if (fenMode || line[0] == '#') pn--;
13440         while (pn > 0) {
13441             /* skip positions before number pn */
13442             if (fgets(line, MSG_SIZ, f) == NULL) {
13443                 Reset(TRUE, TRUE);
13444                 DisplayError(_("Position not found in file"), 0);
13445                 return FALSE;
13446             }
13447             if (fenMode || line[0] == '#') pn--;
13448         }
13449     }
13450
13451     if (fenMode) {
13452         char *p;
13453         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13454             DisplayError(_("Bad FEN position in file"), 0);
13455             return FALSE;
13456         }
13457         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13458             sscanf(p+3, "%s", bestMove);
13459         } else *bestMove = NULLCHAR;
13460     } else {
13461         (void) fgets(line, MSG_SIZ, f);
13462         (void) fgets(line, MSG_SIZ, f);
13463
13464         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13465             (void) fgets(line, MSG_SIZ, f);
13466             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13467                 if (*p == ' ')
13468                   continue;
13469                 initial_position[i][j++] = CharToPiece(*p);
13470             }
13471         }
13472
13473         blackPlaysFirst = FALSE;
13474         if (!feof(f)) {
13475             (void) fgets(line, MSG_SIZ, f);
13476             if (strncmp(line, "black", strlen("black"))==0)
13477               blackPlaysFirst = TRUE;
13478         }
13479     }
13480     startedFromSetupPosition = TRUE;
13481
13482     CopyBoard(boards[0], initial_position);
13483     if (blackPlaysFirst) {
13484         currentMove = forwardMostMove = backwardMostMove = 1;
13485         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13486         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13487         CopyBoard(boards[1], initial_position);
13488         DisplayMessage("", _("Black to play"));
13489     } else {
13490         currentMove = forwardMostMove = backwardMostMove = 0;
13491         DisplayMessage("", _("White to play"));
13492     }
13493     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13494     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13495         SendToProgram("force\n", &first);
13496         SendBoard(&first, forwardMostMove);
13497     }
13498     if (appData.debugMode) {
13499 int i, j;
13500   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13501   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13502         fprintf(debugFP, "Load Position\n");
13503     }
13504
13505     if (positionNumber > 1) {
13506       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13507         DisplayTitle(line);
13508     } else {
13509         DisplayTitle(title);
13510     }
13511     gameMode = EditGame;
13512     ModeHighlight();
13513     ResetClocks();
13514     timeRemaining[0][1] = whiteTimeRemaining;
13515     timeRemaining[1][1] = blackTimeRemaining;
13516     DrawPosition(FALSE, boards[currentMove]);
13517
13518     return TRUE;
13519 }
13520
13521
13522 void
13523 CopyPlayerNameIntoFileName (char **dest, char *src)
13524 {
13525     while (*src != NULLCHAR && *src != ',') {
13526         if (*src == ' ') {
13527             *(*dest)++ = '_';
13528             src++;
13529         } else {
13530             *(*dest)++ = *src++;
13531         }
13532     }
13533 }
13534
13535 char *
13536 DefaultFileName (char *ext)
13537 {
13538     static char def[MSG_SIZ];
13539     char *p;
13540
13541     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13542         p = def;
13543         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13544         *p++ = '-';
13545         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13546         *p++ = '.';
13547         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13548     } else {
13549         def[0] = NULLCHAR;
13550     }
13551     return def;
13552 }
13553
13554 /* Save the current game to the given file */
13555 int
13556 SaveGameToFile (char *filename, int append)
13557 {
13558     FILE *f;
13559     char buf[MSG_SIZ];
13560     int result, i, t,tot=0;
13561
13562     if (strcmp(filename, "-") == 0) {
13563         return SaveGame(stdout, 0, NULL);
13564     } else {
13565         for(i=0; i<10; i++) { // upto 10 tries
13566              f = fopen(filename, append ? "a" : "w");
13567              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13568              if(f || errno != 13) break;
13569              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13570              tot += t;
13571         }
13572         if (f == NULL) {
13573             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13574             DisplayError(buf, errno);
13575             return FALSE;
13576         } else {
13577             safeStrCpy(buf, lastMsg, MSG_SIZ);
13578             DisplayMessage(_("Waiting for access to save file"), "");
13579             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13580             DisplayMessage(_("Saving game"), "");
13581             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13582             result = SaveGame(f, 0, NULL);
13583             DisplayMessage(buf, "");
13584             return result;
13585         }
13586     }
13587 }
13588
13589 char *
13590 SavePart (char *str)
13591 {
13592     static char buf[MSG_SIZ];
13593     char *p;
13594
13595     p = strchr(str, ' ');
13596     if (p == NULL) return str;
13597     strncpy(buf, str, p - str);
13598     buf[p - str] = NULLCHAR;
13599     return buf;
13600 }
13601
13602 #define PGN_MAX_LINE 75
13603
13604 #define PGN_SIDE_WHITE  0
13605 #define PGN_SIDE_BLACK  1
13606
13607 static int
13608 FindFirstMoveOutOfBook (int side)
13609 {
13610     int result = -1;
13611
13612     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13613         int index = backwardMostMove;
13614         int has_book_hit = 0;
13615
13616         if( (index % 2) != side ) {
13617             index++;
13618         }
13619
13620         while( index < forwardMostMove ) {
13621             /* Check to see if engine is in book */
13622             int depth = pvInfoList[index].depth;
13623             int score = pvInfoList[index].score;
13624             int in_book = 0;
13625
13626             if( depth <= 2 ) {
13627                 in_book = 1;
13628             }
13629             else if( score == 0 && depth == 63 ) {
13630                 in_book = 1; /* Zappa */
13631             }
13632             else if( score == 2 && depth == 99 ) {
13633                 in_book = 1; /* Abrok */
13634             }
13635
13636             has_book_hit += in_book;
13637
13638             if( ! in_book ) {
13639                 result = index;
13640
13641                 break;
13642             }
13643
13644             index += 2;
13645         }
13646     }
13647
13648     return result;
13649 }
13650
13651 void
13652 GetOutOfBookInfo (char * buf)
13653 {
13654     int oob[2];
13655     int i;
13656     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13657
13658     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13659     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13660
13661     *buf = '\0';
13662
13663     if( oob[0] >= 0 || oob[1] >= 0 ) {
13664         for( i=0; i<2; i++ ) {
13665             int idx = oob[i];
13666
13667             if( idx >= 0 ) {
13668                 if( i > 0 && oob[0] >= 0 ) {
13669                     strcat( buf, "   " );
13670                 }
13671
13672                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13673                 sprintf( buf+strlen(buf), "%s%.2f",
13674                     pvInfoList[idx].score >= 0 ? "+" : "",
13675                     pvInfoList[idx].score / 100.0 );
13676             }
13677         }
13678     }
13679 }
13680
13681 /* Save game in PGN style */
13682 static void
13683 SaveGamePGN2 (FILE *f)
13684 {
13685     int i, offset, linelen, newblock;
13686 //    char *movetext;
13687     char numtext[32];
13688     int movelen, numlen, blank;
13689     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13690
13691     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13692
13693     PrintPGNTags(f, &gameInfo);
13694
13695     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13696
13697     if (backwardMostMove > 0 || startedFromSetupPosition) {
13698         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13699         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13700         fprintf(f, "\n{--------------\n");
13701         PrintPosition(f, backwardMostMove);
13702         fprintf(f, "--------------}\n");
13703         free(fen);
13704     }
13705     else {
13706         /* [AS] Out of book annotation */
13707         if( appData.saveOutOfBookInfo ) {
13708             char buf[64];
13709
13710             GetOutOfBookInfo( buf );
13711
13712             if( buf[0] != '\0' ) {
13713                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13714             }
13715         }
13716
13717         fprintf(f, "\n");
13718     }
13719
13720     i = backwardMostMove;
13721     linelen = 0;
13722     newblock = TRUE;
13723
13724     while (i < forwardMostMove) {
13725         /* Print comments preceding this move */
13726         if (commentList[i] != NULL) {
13727             if (linelen > 0) fprintf(f, "\n");
13728             fprintf(f, "%s", commentList[i]);
13729             linelen = 0;
13730             newblock = TRUE;
13731         }
13732
13733         /* Format move number */
13734         if ((i % 2) == 0)
13735           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13736         else
13737           if (newblock)
13738             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13739           else
13740             numtext[0] = NULLCHAR;
13741
13742         numlen = strlen(numtext);
13743         newblock = FALSE;
13744
13745         /* Print move number */
13746         blank = linelen > 0 && numlen > 0;
13747         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13748             fprintf(f, "\n");
13749             linelen = 0;
13750             blank = 0;
13751         }
13752         if (blank) {
13753             fprintf(f, " ");
13754             linelen++;
13755         }
13756         fprintf(f, "%s", numtext);
13757         linelen += numlen;
13758
13759         /* Get move */
13760         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13761         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13762
13763         /* Print move */
13764         blank = linelen > 0 && movelen > 0;
13765         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13766             fprintf(f, "\n");
13767             linelen = 0;
13768             blank = 0;
13769         }
13770         if (blank) {
13771             fprintf(f, " ");
13772             linelen++;
13773         }
13774         fprintf(f, "%s", move_buffer);
13775         linelen += movelen;
13776
13777         /* [AS] Add PV info if present */
13778         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13779             /* [HGM] add time */
13780             char buf[MSG_SIZ]; int seconds;
13781
13782             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13783
13784             if( seconds <= 0)
13785               buf[0] = 0;
13786             else
13787               if( seconds < 30 )
13788                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13789               else
13790                 {
13791                   seconds = (seconds + 4)/10; // round to full seconds
13792                   if( seconds < 60 )
13793                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13794                   else
13795                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13796                 }
13797
13798             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13799                       pvInfoList[i].score >= 0 ? "+" : "",
13800                       pvInfoList[i].score / 100.0,
13801                       pvInfoList[i].depth,
13802                       buf );
13803
13804             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13805
13806             /* Print score/depth */
13807             blank = linelen > 0 && movelen > 0;
13808             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13809                 fprintf(f, "\n");
13810                 linelen = 0;
13811                 blank = 0;
13812             }
13813             if (blank) {
13814                 fprintf(f, " ");
13815                 linelen++;
13816             }
13817             fprintf(f, "%s", move_buffer);
13818             linelen += movelen;
13819         }
13820
13821         i++;
13822     }
13823
13824     /* Start a new line */
13825     if (linelen > 0) fprintf(f, "\n");
13826
13827     /* Print comments after last move */
13828     if (commentList[i] != NULL) {
13829         fprintf(f, "%s\n", commentList[i]);
13830     }
13831
13832     /* Print result */
13833     if (gameInfo.resultDetails != NULL &&
13834         gameInfo.resultDetails[0] != NULLCHAR) {
13835         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13836         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13837            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13838             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13839         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13840     } else {
13841         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13842     }
13843 }
13844
13845 /* Save game in PGN style and close the file */
13846 int
13847 SaveGamePGN (FILE *f)
13848 {
13849     SaveGamePGN2(f);
13850     fclose(f);
13851     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13852     return TRUE;
13853 }
13854
13855 /* Save game in old style and close the file */
13856 int
13857 SaveGameOldStyle (FILE *f)
13858 {
13859     int i, offset;
13860     time_t tm;
13861
13862     tm = time((time_t *) NULL);
13863
13864     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13865     PrintOpponents(f);
13866
13867     if (backwardMostMove > 0 || startedFromSetupPosition) {
13868         fprintf(f, "\n[--------------\n");
13869         PrintPosition(f, backwardMostMove);
13870         fprintf(f, "--------------]\n");
13871     } else {
13872         fprintf(f, "\n");
13873     }
13874
13875     i = backwardMostMove;
13876     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13877
13878     while (i < forwardMostMove) {
13879         if (commentList[i] != NULL) {
13880             fprintf(f, "[%s]\n", commentList[i]);
13881         }
13882
13883         if ((i % 2) == 1) {
13884             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13885             i++;
13886         } else {
13887             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13888             i++;
13889             if (commentList[i] != NULL) {
13890                 fprintf(f, "\n");
13891                 continue;
13892             }
13893             if (i >= forwardMostMove) {
13894                 fprintf(f, "\n");
13895                 break;
13896             }
13897             fprintf(f, "%s\n", parseList[i]);
13898             i++;
13899         }
13900     }
13901
13902     if (commentList[i] != NULL) {
13903         fprintf(f, "[%s]\n", commentList[i]);
13904     }
13905
13906     /* This isn't really the old style, but it's close enough */
13907     if (gameInfo.resultDetails != NULL &&
13908         gameInfo.resultDetails[0] != NULLCHAR) {
13909         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13910                 gameInfo.resultDetails);
13911     } else {
13912         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13913     }
13914
13915     fclose(f);
13916     return TRUE;
13917 }
13918
13919 /* Save the current game to open file f and close the file */
13920 int
13921 SaveGame (FILE *f, int dummy, char *dummy2)
13922 {
13923     if (gameMode == EditPosition) EditPositionDone(TRUE);
13924     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13925     if (appData.oldSaveStyle)
13926       return SaveGameOldStyle(f);
13927     else
13928       return SaveGamePGN(f);
13929 }
13930
13931 /* Save the current position to the given file */
13932 int
13933 SavePositionToFile (char *filename)
13934 {
13935     FILE *f;
13936     char buf[MSG_SIZ];
13937
13938     if (strcmp(filename, "-") == 0) {
13939         return SavePosition(stdout, 0, NULL);
13940     } else {
13941         f = fopen(filename, "a");
13942         if (f == NULL) {
13943             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13944             DisplayError(buf, errno);
13945             return FALSE;
13946         } else {
13947             safeStrCpy(buf, lastMsg, MSG_SIZ);
13948             DisplayMessage(_("Waiting for access to save file"), "");
13949             flock(fileno(f), LOCK_EX); // [HGM] lock
13950             DisplayMessage(_("Saving position"), "");
13951             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13952             SavePosition(f, 0, NULL);
13953             DisplayMessage(buf, "");
13954             return TRUE;
13955         }
13956     }
13957 }
13958
13959 /* Save the current position to the given open file and close the file */
13960 int
13961 SavePosition (FILE *f, int dummy, char *dummy2)
13962 {
13963     time_t tm;
13964     char *fen;
13965
13966     if (gameMode == EditPosition) EditPositionDone(TRUE);
13967     if (appData.oldSaveStyle) {
13968         tm = time((time_t *) NULL);
13969
13970         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13971         PrintOpponents(f);
13972         fprintf(f, "[--------------\n");
13973         PrintPosition(f, currentMove);
13974         fprintf(f, "--------------]\n");
13975     } else {
13976         fen = PositionToFEN(currentMove, NULL, 1);
13977         fprintf(f, "%s\n", fen);
13978         free(fen);
13979     }
13980     fclose(f);
13981     return TRUE;
13982 }
13983
13984 void
13985 ReloadCmailMsgEvent (int unregister)
13986 {
13987 #if !WIN32
13988     static char *inFilename = NULL;
13989     static char *outFilename;
13990     int i;
13991     struct stat inbuf, outbuf;
13992     int status;
13993
13994     /* Any registered moves are unregistered if unregister is set, */
13995     /* i.e. invoked by the signal handler */
13996     if (unregister) {
13997         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13998             cmailMoveRegistered[i] = FALSE;
13999             if (cmailCommentList[i] != NULL) {
14000                 free(cmailCommentList[i]);
14001                 cmailCommentList[i] = NULL;
14002             }
14003         }
14004         nCmailMovesRegistered = 0;
14005     }
14006
14007     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14008         cmailResult[i] = CMAIL_NOT_RESULT;
14009     }
14010     nCmailResults = 0;
14011
14012     if (inFilename == NULL) {
14013         /* Because the filenames are static they only get malloced once  */
14014         /* and they never get freed                                      */
14015         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14016         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14017
14018         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14019         sprintf(outFilename, "%s.out", appData.cmailGameName);
14020     }
14021
14022     status = stat(outFilename, &outbuf);
14023     if (status < 0) {
14024         cmailMailedMove = FALSE;
14025     } else {
14026         status = stat(inFilename, &inbuf);
14027         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14028     }
14029
14030     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14031        counts the games, notes how each one terminated, etc.
14032
14033        It would be nice to remove this kludge and instead gather all
14034        the information while building the game list.  (And to keep it
14035        in the game list nodes instead of having a bunch of fixed-size
14036        parallel arrays.)  Note this will require getting each game's
14037        termination from the PGN tags, as the game list builder does
14038        not process the game moves.  --mann
14039        */
14040     cmailMsgLoaded = TRUE;
14041     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14042
14043     /* Load first game in the file or popup game menu */
14044     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14045
14046 #endif /* !WIN32 */
14047     return;
14048 }
14049
14050 int
14051 RegisterMove ()
14052 {
14053     FILE *f;
14054     char string[MSG_SIZ];
14055
14056     if (   cmailMailedMove
14057         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14058         return TRUE;            /* Allow free viewing  */
14059     }
14060
14061     /* Unregister move to ensure that we don't leave RegisterMove        */
14062     /* with the move registered when the conditions for registering no   */
14063     /* longer hold                                                       */
14064     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14065         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14066         nCmailMovesRegistered --;
14067
14068         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14069           {
14070               free(cmailCommentList[lastLoadGameNumber - 1]);
14071               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14072           }
14073     }
14074
14075     if (cmailOldMove == -1) {
14076         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14077         return FALSE;
14078     }
14079
14080     if (currentMove > cmailOldMove + 1) {
14081         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14082         return FALSE;
14083     }
14084
14085     if (currentMove < cmailOldMove) {
14086         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14087         return FALSE;
14088     }
14089
14090     if (forwardMostMove > currentMove) {
14091         /* Silently truncate extra moves */
14092         TruncateGame();
14093     }
14094
14095     if (   (currentMove == cmailOldMove + 1)
14096         || (   (currentMove == cmailOldMove)
14097             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14098                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14099         if (gameInfo.result != GameUnfinished) {
14100             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14101         }
14102
14103         if (commentList[currentMove] != NULL) {
14104             cmailCommentList[lastLoadGameNumber - 1]
14105               = StrSave(commentList[currentMove]);
14106         }
14107         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14108
14109         if (appData.debugMode)
14110           fprintf(debugFP, "Saving %s for game %d\n",
14111                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14112
14113         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14114
14115         f = fopen(string, "w");
14116         if (appData.oldSaveStyle) {
14117             SaveGameOldStyle(f); /* also closes the file */
14118
14119             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14120             f = fopen(string, "w");
14121             SavePosition(f, 0, NULL); /* also closes the file */
14122         } else {
14123             fprintf(f, "{--------------\n");
14124             PrintPosition(f, currentMove);
14125             fprintf(f, "--------------}\n\n");
14126
14127             SaveGame(f, 0, NULL); /* also closes the file*/
14128         }
14129
14130         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14131         nCmailMovesRegistered ++;
14132     } else if (nCmailGames == 1) {
14133         DisplayError(_("You have not made a move yet"), 0);
14134         return FALSE;
14135     }
14136
14137     return TRUE;
14138 }
14139
14140 void
14141 MailMoveEvent ()
14142 {
14143 #if !WIN32
14144     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14145     FILE *commandOutput;
14146     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14147     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14148     int nBuffers;
14149     int i;
14150     int archived;
14151     char *arcDir;
14152
14153     if (! cmailMsgLoaded) {
14154         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14155         return;
14156     }
14157
14158     if (nCmailGames == nCmailResults) {
14159         DisplayError(_("No unfinished games"), 0);
14160         return;
14161     }
14162
14163 #if CMAIL_PROHIBIT_REMAIL
14164     if (cmailMailedMove) {
14165       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);
14166         DisplayError(msg, 0);
14167         return;
14168     }
14169 #endif
14170
14171     if (! (cmailMailedMove || RegisterMove())) return;
14172
14173     if (   cmailMailedMove
14174         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14175       snprintf(string, MSG_SIZ, partCommandString,
14176                appData.debugMode ? " -v" : "", appData.cmailGameName);
14177         commandOutput = popen(string, "r");
14178
14179         if (commandOutput == NULL) {
14180             DisplayError(_("Failed to invoke cmail"), 0);
14181         } else {
14182             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14183                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14184             }
14185             if (nBuffers > 1) {
14186                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14187                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14188                 nBytes = MSG_SIZ - 1;
14189             } else {
14190                 (void) memcpy(msg, buffer, nBytes);
14191             }
14192             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14193
14194             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14195                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14196
14197                 archived = TRUE;
14198                 for (i = 0; i < nCmailGames; i ++) {
14199                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14200                         archived = FALSE;
14201                     }
14202                 }
14203                 if (   archived
14204                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14205                         != NULL)) {
14206                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14207                            arcDir,
14208                            appData.cmailGameName,
14209                            gameInfo.date);
14210                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14211                     cmailMsgLoaded = FALSE;
14212                 }
14213             }
14214
14215             DisplayInformation(msg);
14216             pclose(commandOutput);
14217         }
14218     } else {
14219         if ((*cmailMsg) != '\0') {
14220             DisplayInformation(cmailMsg);
14221         }
14222     }
14223
14224     return;
14225 #endif /* !WIN32 */
14226 }
14227
14228 char *
14229 CmailMsg ()
14230 {
14231 #if WIN32
14232     return NULL;
14233 #else
14234     int  prependComma = 0;
14235     char number[5];
14236     char string[MSG_SIZ];       /* Space for game-list */
14237     int  i;
14238
14239     if (!cmailMsgLoaded) return "";
14240
14241     if (cmailMailedMove) {
14242       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14243     } else {
14244         /* Create a list of games left */
14245       snprintf(string, MSG_SIZ, "[");
14246         for (i = 0; i < nCmailGames; i ++) {
14247             if (! (   cmailMoveRegistered[i]
14248                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14249                 if (prependComma) {
14250                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14251                 } else {
14252                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14253                     prependComma = 1;
14254                 }
14255
14256                 strcat(string, number);
14257             }
14258         }
14259         strcat(string, "]");
14260
14261         if (nCmailMovesRegistered + nCmailResults == 0) {
14262             switch (nCmailGames) {
14263               case 1:
14264                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14265                 break;
14266
14267               case 2:
14268                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14269                 break;
14270
14271               default:
14272                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14273                          nCmailGames);
14274                 break;
14275             }
14276         } else {
14277             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14278               case 1:
14279                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14280                          string);
14281                 break;
14282
14283               case 0:
14284                 if (nCmailResults == nCmailGames) {
14285                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14286                 } else {
14287                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14288                 }
14289                 break;
14290
14291               default:
14292                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14293                          string);
14294             }
14295         }
14296     }
14297     return cmailMsg;
14298 #endif /* WIN32 */
14299 }
14300
14301 void
14302 ResetGameEvent ()
14303 {
14304     if (gameMode == Training)
14305       SetTrainingModeOff();
14306
14307     Reset(TRUE, TRUE);
14308     cmailMsgLoaded = FALSE;
14309     if (appData.icsActive) {
14310       SendToICS(ics_prefix);
14311       SendToICS("refresh\n");
14312     }
14313 }
14314
14315 void
14316 ExitEvent (int status)
14317 {
14318     exiting++;
14319     if (exiting > 2) {
14320       /* Give up on clean exit */
14321       exit(status);
14322     }
14323     if (exiting > 1) {
14324       /* Keep trying for clean exit */
14325       return;
14326     }
14327
14328     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14329     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14330
14331     if (telnetISR != NULL) {
14332       RemoveInputSource(telnetISR);
14333     }
14334     if (icsPR != NoProc) {
14335       DestroyChildProcess(icsPR, TRUE);
14336     }
14337
14338     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14339     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14340
14341     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14342     /* make sure this other one finishes before killing it!                  */
14343     if(endingGame) { int count = 0;
14344         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14345         while(endingGame && count++ < 10) DoSleep(1);
14346         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14347     }
14348
14349     /* Kill off chess programs */
14350     if (first.pr != NoProc) {
14351         ExitAnalyzeMode();
14352
14353         DoSleep( appData.delayBeforeQuit );
14354         SendToProgram("quit\n", &first);
14355         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14356     }
14357     if (second.pr != NoProc) {
14358         DoSleep( appData.delayBeforeQuit );
14359         SendToProgram("quit\n", &second);
14360         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14361     }
14362     if (first.isr != NULL) {
14363         RemoveInputSource(first.isr);
14364     }
14365     if (second.isr != NULL) {
14366         RemoveInputSource(second.isr);
14367     }
14368
14369     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14370     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14371
14372     ShutDownFrontEnd();
14373     exit(status);
14374 }
14375
14376 void
14377 PauseEngine (ChessProgramState *cps)
14378 {
14379     SendToProgram("pause\n", cps);
14380     cps->pause = 2;
14381 }
14382
14383 void
14384 UnPauseEngine (ChessProgramState *cps)
14385 {
14386     SendToProgram("resume\n", cps);
14387     cps->pause = 1;
14388 }
14389
14390 void
14391 PauseEvent ()
14392 {
14393     if (appData.debugMode)
14394         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14395     if (pausing) {
14396         pausing = FALSE;
14397         ModeHighlight();
14398         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14399             StartClocks();
14400             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14401                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14402                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14403             }
14404             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14405             HandleMachineMove(stashedInputMove, stalledEngine);
14406             stalledEngine = NULL;
14407             return;
14408         }
14409         if (gameMode == MachinePlaysWhite ||
14410             gameMode == TwoMachinesPlay   ||
14411             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14412             if(first.pause)  UnPauseEngine(&first);
14413             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14414             if(second.pause) UnPauseEngine(&second);
14415             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14416             StartClocks();
14417         } else {
14418             DisplayBothClocks();
14419         }
14420         if (gameMode == PlayFromGameFile) {
14421             if (appData.timeDelay >= 0)
14422                 AutoPlayGameLoop();
14423         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14424             Reset(FALSE, TRUE);
14425             SendToICS(ics_prefix);
14426             SendToICS("refresh\n");
14427         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14428             ForwardInner(forwardMostMove);
14429         }
14430         pauseExamInvalid = FALSE;
14431     } else {
14432         switch (gameMode) {
14433           default:
14434             return;
14435           case IcsExamining:
14436             pauseExamForwardMostMove = forwardMostMove;
14437             pauseExamInvalid = FALSE;
14438             /* fall through */
14439           case IcsObserving:
14440           case IcsPlayingWhite:
14441           case IcsPlayingBlack:
14442             pausing = TRUE;
14443             ModeHighlight();
14444             return;
14445           case PlayFromGameFile:
14446             (void) StopLoadGameTimer();
14447             pausing = TRUE;
14448             ModeHighlight();
14449             break;
14450           case BeginningOfGame:
14451             if (appData.icsActive) return;
14452             /* else fall through */
14453           case MachinePlaysWhite:
14454           case MachinePlaysBlack:
14455           case TwoMachinesPlay:
14456             if (forwardMostMove == 0)
14457               return;           /* don't pause if no one has moved */
14458             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14459                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14460                 if(onMove->pause) {           // thinking engine can be paused
14461                     PauseEngine(onMove);      // do it
14462                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14463                         PauseEngine(onMove->other);
14464                     else
14465                         SendToProgram("easy\n", onMove->other);
14466                     StopClocks();
14467                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14468             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14469                 if(first.pause) {
14470                     PauseEngine(&first);
14471                     StopClocks();
14472                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14473             } else { // human on move, pause pondering by either method
14474                 if(first.pause)
14475                     PauseEngine(&first);
14476                 else if(appData.ponderNextMove)
14477                     SendToProgram("easy\n", &first);
14478                 StopClocks();
14479             }
14480             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14481           case AnalyzeMode:
14482             pausing = TRUE;
14483             ModeHighlight();
14484             break;
14485         }
14486     }
14487 }
14488
14489 void
14490 EditCommentEvent ()
14491 {
14492     char title[MSG_SIZ];
14493
14494     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14495       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14496     } else {
14497       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14498                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14499                parseList[currentMove - 1]);
14500     }
14501
14502     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14503 }
14504
14505
14506 void
14507 EditTagsEvent ()
14508 {
14509     char *tags = PGNTags(&gameInfo);
14510     bookUp = FALSE;
14511     EditTagsPopUp(tags, NULL);
14512     free(tags);
14513 }
14514
14515 void
14516 ToggleSecond ()
14517 {
14518   if(second.analyzing) {
14519     SendToProgram("exit\n", &second);
14520     second.analyzing = FALSE;
14521   } else {
14522     if (second.pr == NoProc) StartChessProgram(&second);
14523     InitChessProgram(&second, FALSE);
14524     FeedMovesToProgram(&second, currentMove);
14525
14526     SendToProgram("analyze\n", &second);
14527     second.analyzing = TRUE;
14528   }
14529 }
14530
14531 /* Toggle ShowThinking */
14532 void
14533 ToggleShowThinking()
14534 {
14535   appData.showThinking = !appData.showThinking;
14536   ShowThinkingEvent();
14537 }
14538
14539 int
14540 AnalyzeModeEvent ()
14541 {
14542     char buf[MSG_SIZ];
14543
14544     if (!first.analysisSupport) {
14545       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14546       DisplayError(buf, 0);
14547       return 0;
14548     }
14549     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14550     if (appData.icsActive) {
14551         if (gameMode != IcsObserving) {
14552           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14553             DisplayError(buf, 0);
14554             /* secure check */
14555             if (appData.icsEngineAnalyze) {
14556                 if (appData.debugMode)
14557                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14558                 ExitAnalyzeMode();
14559                 ModeHighlight();
14560             }
14561             return 0;
14562         }
14563         /* if enable, user wants to disable icsEngineAnalyze */
14564         if (appData.icsEngineAnalyze) {
14565                 ExitAnalyzeMode();
14566                 ModeHighlight();
14567                 return 0;
14568         }
14569         appData.icsEngineAnalyze = TRUE;
14570         if (appData.debugMode)
14571             fprintf(debugFP, "ICS engine analyze starting... \n");
14572     }
14573
14574     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14575     if (appData.noChessProgram || gameMode == AnalyzeMode)
14576       return 0;
14577
14578     if (gameMode != AnalyzeFile) {
14579         if (!appData.icsEngineAnalyze) {
14580                EditGameEvent();
14581                if (gameMode != EditGame) return 0;
14582         }
14583         if (!appData.showThinking) ToggleShowThinking();
14584         ResurrectChessProgram();
14585         SendToProgram("analyze\n", &first);
14586         first.analyzing = TRUE;
14587         /*first.maybeThinking = TRUE;*/
14588         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14589         EngineOutputPopUp();
14590     }
14591     if (!appData.icsEngineAnalyze) {
14592         gameMode = AnalyzeMode;
14593         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14594     }
14595     pausing = FALSE;
14596     ModeHighlight();
14597     SetGameInfo();
14598
14599     StartAnalysisClock();
14600     GetTimeMark(&lastNodeCountTime);
14601     lastNodeCount = 0;
14602     return 1;
14603 }
14604
14605 void
14606 AnalyzeFileEvent ()
14607 {
14608     if (appData.noChessProgram || gameMode == AnalyzeFile)
14609       return;
14610
14611     if (!first.analysisSupport) {
14612       char buf[MSG_SIZ];
14613       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14614       DisplayError(buf, 0);
14615       return;
14616     }
14617
14618     if (gameMode != AnalyzeMode) {
14619         keepInfo = 1; // mere annotating should not alter PGN tags
14620         EditGameEvent();
14621         keepInfo = 0;
14622         if (gameMode != EditGame) return;
14623         if (!appData.showThinking) ToggleShowThinking();
14624         ResurrectChessProgram();
14625         SendToProgram("analyze\n", &first);
14626         first.analyzing = TRUE;
14627         /*first.maybeThinking = TRUE;*/
14628         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14629         EngineOutputPopUp();
14630     }
14631     gameMode = AnalyzeFile;
14632     pausing = FALSE;
14633     ModeHighlight();
14634
14635     StartAnalysisClock();
14636     GetTimeMark(&lastNodeCountTime);
14637     lastNodeCount = 0;
14638     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14639     AnalysisPeriodicEvent(1);
14640 }
14641
14642 void
14643 MachineWhiteEvent ()
14644 {
14645     char buf[MSG_SIZ];
14646     char *bookHit = NULL;
14647
14648     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14649       return;
14650
14651
14652     if (gameMode == PlayFromGameFile ||
14653         gameMode == TwoMachinesPlay  ||
14654         gameMode == Training         ||
14655         gameMode == AnalyzeMode      ||
14656         gameMode == EndOfGame)
14657         EditGameEvent();
14658
14659     if (gameMode == EditPosition)
14660         EditPositionDone(TRUE);
14661
14662     if (!WhiteOnMove(currentMove)) {
14663         DisplayError(_("It is not White's turn"), 0);
14664         return;
14665     }
14666
14667     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14668       ExitAnalyzeMode();
14669
14670     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14671         gameMode == AnalyzeFile)
14672         TruncateGame();
14673
14674     ResurrectChessProgram();    /* in case it isn't running */
14675     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14676         gameMode = MachinePlaysWhite;
14677         ResetClocks();
14678     } else
14679     gameMode = MachinePlaysWhite;
14680     pausing = FALSE;
14681     ModeHighlight();
14682     SetGameInfo();
14683     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14684     DisplayTitle(buf);
14685     if (first.sendName) {
14686       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14687       SendToProgram(buf, &first);
14688     }
14689     if (first.sendTime) {
14690       if (first.useColors) {
14691         SendToProgram("black\n", &first); /*gnu kludge*/
14692       }
14693       SendTimeRemaining(&first, TRUE);
14694     }
14695     if (first.useColors) {
14696       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14697     }
14698     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14699     SetMachineThinkingEnables();
14700     first.maybeThinking = TRUE;
14701     StartClocks();
14702     firstMove = FALSE;
14703
14704     if (appData.autoFlipView && !flipView) {
14705       flipView = !flipView;
14706       DrawPosition(FALSE, NULL);
14707       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14708     }
14709
14710     if(bookHit) { // [HGM] book: simulate book reply
14711         static char bookMove[MSG_SIZ]; // a bit generous?
14712
14713         programStats.nodes = programStats.depth = programStats.time =
14714         programStats.score = programStats.got_only_move = 0;
14715         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14716
14717         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14718         strcat(bookMove, bookHit);
14719         HandleMachineMove(bookMove, &first);
14720     }
14721 }
14722
14723 void
14724 MachineBlackEvent ()
14725 {
14726   char buf[MSG_SIZ];
14727   char *bookHit = NULL;
14728
14729     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14730         return;
14731
14732
14733     if (gameMode == PlayFromGameFile ||
14734         gameMode == TwoMachinesPlay  ||
14735         gameMode == Training         ||
14736         gameMode == AnalyzeMode      ||
14737         gameMode == EndOfGame)
14738         EditGameEvent();
14739
14740     if (gameMode == EditPosition)
14741         EditPositionDone(TRUE);
14742
14743     if (WhiteOnMove(currentMove)) {
14744         DisplayError(_("It is not Black's turn"), 0);
14745         return;
14746     }
14747
14748     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14749       ExitAnalyzeMode();
14750
14751     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14752         gameMode == AnalyzeFile)
14753         TruncateGame();
14754
14755     ResurrectChessProgram();    /* in case it isn't running */
14756     gameMode = MachinePlaysBlack;
14757     pausing = FALSE;
14758     ModeHighlight();
14759     SetGameInfo();
14760     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14761     DisplayTitle(buf);
14762     if (first.sendName) {
14763       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14764       SendToProgram(buf, &first);
14765     }
14766     if (first.sendTime) {
14767       if (first.useColors) {
14768         SendToProgram("white\n", &first); /*gnu kludge*/
14769       }
14770       SendTimeRemaining(&first, FALSE);
14771     }
14772     if (first.useColors) {
14773       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14774     }
14775     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14776     SetMachineThinkingEnables();
14777     first.maybeThinking = TRUE;
14778     StartClocks();
14779
14780     if (appData.autoFlipView && flipView) {
14781       flipView = !flipView;
14782       DrawPosition(FALSE, NULL);
14783       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14784     }
14785     if(bookHit) { // [HGM] book: simulate book reply
14786         static char bookMove[MSG_SIZ]; // a bit generous?
14787
14788         programStats.nodes = programStats.depth = programStats.time =
14789         programStats.score = programStats.got_only_move = 0;
14790         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14791
14792         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14793         strcat(bookMove, bookHit);
14794         HandleMachineMove(bookMove, &first);
14795     }
14796 }
14797
14798
14799 void
14800 DisplayTwoMachinesTitle ()
14801 {
14802     char buf[MSG_SIZ];
14803     if (appData.matchGames > 0) {
14804         if(appData.tourneyFile[0]) {
14805           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14806                    gameInfo.white, _("vs."), gameInfo.black,
14807                    nextGame+1, appData.matchGames+1,
14808                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14809         } else
14810         if (first.twoMachinesColor[0] == 'w') {
14811           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14812                    gameInfo.white, _("vs."),  gameInfo.black,
14813                    first.matchWins, second.matchWins,
14814                    matchGame - 1 - (first.matchWins + second.matchWins));
14815         } else {
14816           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14817                    gameInfo.white, _("vs."), gameInfo.black,
14818                    second.matchWins, first.matchWins,
14819                    matchGame - 1 - (first.matchWins + second.matchWins));
14820         }
14821     } else {
14822       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14823     }
14824     DisplayTitle(buf);
14825 }
14826
14827 void
14828 SettingsMenuIfReady ()
14829 {
14830   if (second.lastPing != second.lastPong) {
14831     DisplayMessage("", _("Waiting for second chess program"));
14832     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14833     return;
14834   }
14835   ThawUI();
14836   DisplayMessage("", "");
14837   SettingsPopUp(&second);
14838 }
14839
14840 int
14841 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14842 {
14843     char buf[MSG_SIZ];
14844     if (cps->pr == NoProc) {
14845         StartChessProgram(cps);
14846         if (cps->protocolVersion == 1) {
14847           retry();
14848           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14849         } else {
14850           /* kludge: allow timeout for initial "feature" command */
14851           if(retry != TwoMachinesEventIfReady) FreezeUI();
14852           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14853           DisplayMessage("", buf);
14854           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14855         }
14856         return 1;
14857     }
14858     return 0;
14859 }
14860
14861 void
14862 TwoMachinesEvent P((void))
14863 {
14864     int i;
14865     char buf[MSG_SIZ];
14866     ChessProgramState *onmove;
14867     char *bookHit = NULL;
14868     static int stalling = 0;
14869     TimeMark now;
14870     long wait;
14871
14872     if (appData.noChessProgram) return;
14873
14874     switch (gameMode) {
14875       case TwoMachinesPlay:
14876         return;
14877       case MachinePlaysWhite:
14878       case MachinePlaysBlack:
14879         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14880             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14881             return;
14882         }
14883         /* fall through */
14884       case BeginningOfGame:
14885       case PlayFromGameFile:
14886       case EndOfGame:
14887         EditGameEvent();
14888         if (gameMode != EditGame) return;
14889         break;
14890       case EditPosition:
14891         EditPositionDone(TRUE);
14892         break;
14893       case AnalyzeMode:
14894       case AnalyzeFile:
14895         ExitAnalyzeMode();
14896         break;
14897       case EditGame:
14898       default:
14899         break;
14900     }
14901
14902 //    forwardMostMove = currentMove;
14903     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14904     startingEngine = TRUE;
14905
14906     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14907
14908     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14909     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14910       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14911       return;
14912     }
14913     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14914
14915     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14916                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14917         startingEngine = matchMode = FALSE;
14918         DisplayError("second engine does not play this", 0);
14919         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14920         EditGameEvent(); // switch back to EditGame mode
14921         return;
14922     }
14923
14924     if(!stalling) {
14925       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14926       SendToProgram("force\n", &second);
14927       stalling = 1;
14928       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14929       return;
14930     }
14931     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14932     if(appData.matchPause>10000 || appData.matchPause<10)
14933                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14934     wait = SubtractTimeMarks(&now, &pauseStart);
14935     if(wait < appData.matchPause) {
14936         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14937         return;
14938     }
14939     // we are now committed to starting the game
14940     stalling = 0;
14941     DisplayMessage("", "");
14942     if (startedFromSetupPosition) {
14943         SendBoard(&second, backwardMostMove);
14944     if (appData.debugMode) {
14945         fprintf(debugFP, "Two Machines\n");
14946     }
14947     }
14948     for (i = backwardMostMove; i < forwardMostMove; i++) {
14949         SendMoveToProgram(i, &second);
14950     }
14951
14952     gameMode = TwoMachinesPlay;
14953     pausing = startingEngine = FALSE;
14954     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14955     SetGameInfo();
14956     DisplayTwoMachinesTitle();
14957     firstMove = TRUE;
14958     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14959         onmove = &first;
14960     } else {
14961         onmove = &second;
14962     }
14963     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14964     SendToProgram(first.computerString, &first);
14965     if (first.sendName) {
14966       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14967       SendToProgram(buf, &first);
14968     }
14969     SendToProgram(second.computerString, &second);
14970     if (second.sendName) {
14971       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14972       SendToProgram(buf, &second);
14973     }
14974
14975     ResetClocks();
14976     if (!first.sendTime || !second.sendTime) {
14977         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14978         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14979     }
14980     if (onmove->sendTime) {
14981       if (onmove->useColors) {
14982         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14983       }
14984       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14985     }
14986     if (onmove->useColors) {
14987       SendToProgram(onmove->twoMachinesColor, onmove);
14988     }
14989     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14990 //    SendToProgram("go\n", onmove);
14991     onmove->maybeThinking = TRUE;
14992     SetMachineThinkingEnables();
14993
14994     StartClocks();
14995
14996     if(bookHit) { // [HGM] book: simulate book reply
14997         static char bookMove[MSG_SIZ]; // a bit generous?
14998
14999         programStats.nodes = programStats.depth = programStats.time =
15000         programStats.score = programStats.got_only_move = 0;
15001         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15002
15003         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15004         strcat(bookMove, bookHit);
15005         savedMessage = bookMove; // args for deferred call
15006         savedState = onmove;
15007         ScheduleDelayedEvent(DeferredBookMove, 1);
15008     }
15009 }
15010
15011 void
15012 TrainingEvent ()
15013 {
15014     if (gameMode == Training) {
15015       SetTrainingModeOff();
15016       gameMode = PlayFromGameFile;
15017       DisplayMessage("", _("Training mode off"));
15018     } else {
15019       gameMode = Training;
15020       animateTraining = appData.animate;
15021
15022       /* make sure we are not already at the end of the game */
15023       if (currentMove < forwardMostMove) {
15024         SetTrainingModeOn();
15025         DisplayMessage("", _("Training mode on"));
15026       } else {
15027         gameMode = PlayFromGameFile;
15028         DisplayError(_("Already at end of game"), 0);
15029       }
15030     }
15031     ModeHighlight();
15032 }
15033
15034 void
15035 IcsClientEvent ()
15036 {
15037     if (!appData.icsActive) return;
15038     switch (gameMode) {
15039       case IcsPlayingWhite:
15040       case IcsPlayingBlack:
15041       case IcsObserving:
15042       case IcsIdle:
15043       case BeginningOfGame:
15044       case IcsExamining:
15045         return;
15046
15047       case EditGame:
15048         break;
15049
15050       case EditPosition:
15051         EditPositionDone(TRUE);
15052         break;
15053
15054       case AnalyzeMode:
15055       case AnalyzeFile:
15056         ExitAnalyzeMode();
15057         break;
15058
15059       default:
15060         EditGameEvent();
15061         break;
15062     }
15063
15064     gameMode = IcsIdle;
15065     ModeHighlight();
15066     return;
15067 }
15068
15069 void
15070 EditGameEvent ()
15071 {
15072     int i;
15073
15074     switch (gameMode) {
15075       case Training:
15076         SetTrainingModeOff();
15077         break;
15078       case MachinePlaysWhite:
15079       case MachinePlaysBlack:
15080       case BeginningOfGame:
15081         SendToProgram("force\n", &first);
15082         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15083             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15084                 char buf[MSG_SIZ];
15085                 abortEngineThink = TRUE;
15086                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15087                 SendToProgram(buf, &first);
15088                 DisplayMessage("Aborting engine think", "");
15089                 FreezeUI();
15090             }
15091         }
15092         SetUserThinkingEnables();
15093         break;
15094       case PlayFromGameFile:
15095         (void) StopLoadGameTimer();
15096         if (gameFileFP != NULL) {
15097             gameFileFP = NULL;
15098         }
15099         break;
15100       case EditPosition:
15101         EditPositionDone(TRUE);
15102         break;
15103       case AnalyzeMode:
15104       case AnalyzeFile:
15105         ExitAnalyzeMode();
15106         SendToProgram("force\n", &first);
15107         break;
15108       case TwoMachinesPlay:
15109         GameEnds(EndOfFile, NULL, GE_PLAYER);
15110         ResurrectChessProgram();
15111         SetUserThinkingEnables();
15112         break;
15113       case EndOfGame:
15114         ResurrectChessProgram();
15115         break;
15116       case IcsPlayingBlack:
15117       case IcsPlayingWhite:
15118         DisplayError(_("Warning: You are still playing a game"), 0);
15119         break;
15120       case IcsObserving:
15121         DisplayError(_("Warning: You are still observing a game"), 0);
15122         break;
15123       case IcsExamining:
15124         DisplayError(_("Warning: You are still examining a game"), 0);
15125         break;
15126       case IcsIdle:
15127         break;
15128       case EditGame:
15129       default:
15130         return;
15131     }
15132
15133     pausing = FALSE;
15134     StopClocks();
15135     first.offeredDraw = second.offeredDraw = 0;
15136
15137     if (gameMode == PlayFromGameFile) {
15138         whiteTimeRemaining = timeRemaining[0][currentMove];
15139         blackTimeRemaining = timeRemaining[1][currentMove];
15140         DisplayTitle("");
15141     }
15142
15143     if (gameMode == MachinePlaysWhite ||
15144         gameMode == MachinePlaysBlack ||
15145         gameMode == TwoMachinesPlay ||
15146         gameMode == EndOfGame) {
15147         i = forwardMostMove;
15148         while (i > currentMove) {
15149             SendToProgram("undo\n", &first);
15150             i--;
15151         }
15152         if(!adjustedClock) {
15153         whiteTimeRemaining = timeRemaining[0][currentMove];
15154         blackTimeRemaining = timeRemaining[1][currentMove];
15155         DisplayBothClocks();
15156         }
15157         if (whiteFlag || blackFlag) {
15158             whiteFlag = blackFlag = 0;
15159         }
15160         DisplayTitle("");
15161     }
15162
15163     gameMode = EditGame;
15164     ModeHighlight();
15165     SetGameInfo();
15166 }
15167
15168
15169 void
15170 EditPositionEvent ()
15171 {
15172     if (gameMode == EditPosition) {
15173         EditGameEvent();
15174         return;
15175     }
15176
15177     EditGameEvent();
15178     if (gameMode != EditGame) return;
15179
15180     gameMode = EditPosition;
15181     ModeHighlight();
15182     SetGameInfo();
15183     if (currentMove > 0)
15184       CopyBoard(boards[0], boards[currentMove]);
15185
15186     blackPlaysFirst = !WhiteOnMove(currentMove);
15187     ResetClocks();
15188     currentMove = forwardMostMove = backwardMostMove = 0;
15189     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15190     DisplayMove(-1);
15191     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15192 }
15193
15194 void
15195 ExitAnalyzeMode ()
15196 {
15197     /* [DM] icsEngineAnalyze - possible call from other functions */
15198     if (appData.icsEngineAnalyze) {
15199         appData.icsEngineAnalyze = FALSE;
15200
15201         DisplayMessage("",_("Close ICS engine analyze..."));
15202     }
15203     if (first.analysisSupport && first.analyzing) {
15204       SendToBoth("exit\n");
15205       first.analyzing = second.analyzing = FALSE;
15206     }
15207     thinkOutput[0] = NULLCHAR;
15208 }
15209
15210 void
15211 EditPositionDone (Boolean fakeRights)
15212 {
15213     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15214
15215     startedFromSetupPosition = TRUE;
15216     InitChessProgram(&first, FALSE);
15217     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15218       boards[0][EP_STATUS] = EP_NONE;
15219       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15220       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15221         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15222         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15223       } else boards[0][CASTLING][2] = NoRights;
15224       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15225         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15226         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15227       } else boards[0][CASTLING][5] = NoRights;
15228       if(gameInfo.variant == VariantSChess) {
15229         int i;
15230         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15231           boards[0][VIRGIN][i] = 0;
15232           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15233           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15234         }
15235       }
15236     }
15237     SendToProgram("force\n", &first);
15238     if (blackPlaysFirst) {
15239         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15240         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15241         currentMove = forwardMostMove = backwardMostMove = 1;
15242         CopyBoard(boards[1], boards[0]);
15243     } else {
15244         currentMove = forwardMostMove = backwardMostMove = 0;
15245     }
15246     SendBoard(&first, forwardMostMove);
15247     if (appData.debugMode) {
15248         fprintf(debugFP, "EditPosDone\n");
15249     }
15250     DisplayTitle("");
15251     DisplayMessage("", "");
15252     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15253     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15254     gameMode = EditGame;
15255     ModeHighlight();
15256     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15257     ClearHighlights(); /* [AS] */
15258 }
15259
15260 /* Pause for `ms' milliseconds */
15261 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15262 void
15263 TimeDelay (long ms)
15264 {
15265     TimeMark m1, m2;
15266
15267     GetTimeMark(&m1);
15268     do {
15269         GetTimeMark(&m2);
15270     } while (SubtractTimeMarks(&m2, &m1) < ms);
15271 }
15272
15273 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15274 void
15275 SendMultiLineToICS (char *buf)
15276 {
15277     char temp[MSG_SIZ+1], *p;
15278     int len;
15279
15280     len = strlen(buf);
15281     if (len > MSG_SIZ)
15282       len = MSG_SIZ;
15283
15284     strncpy(temp, buf, len);
15285     temp[len] = 0;
15286
15287     p = temp;
15288     while (*p) {
15289         if (*p == '\n' || *p == '\r')
15290           *p = ' ';
15291         ++p;
15292     }
15293
15294     strcat(temp, "\n");
15295     SendToICS(temp);
15296     SendToPlayer(temp, strlen(temp));
15297 }
15298
15299 void
15300 SetWhiteToPlayEvent ()
15301 {
15302     if (gameMode == EditPosition) {
15303         blackPlaysFirst = FALSE;
15304         DisplayBothClocks();    /* works because currentMove is 0 */
15305     } else if (gameMode == IcsExamining) {
15306         SendToICS(ics_prefix);
15307         SendToICS("tomove white\n");
15308     }
15309 }
15310
15311 void
15312 SetBlackToPlayEvent ()
15313 {
15314     if (gameMode == EditPosition) {
15315         blackPlaysFirst = TRUE;
15316         currentMove = 1;        /* kludge */
15317         DisplayBothClocks();
15318         currentMove = 0;
15319     } else if (gameMode == IcsExamining) {
15320         SendToICS(ics_prefix);
15321         SendToICS("tomove black\n");
15322     }
15323 }
15324
15325 void
15326 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15327 {
15328     char buf[MSG_SIZ];
15329     ChessSquare piece = boards[0][y][x];
15330     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15331     static int lastVariant;
15332
15333     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15334
15335     switch (selection) {
15336       case ClearBoard:
15337         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15338         MarkTargetSquares(1);
15339         CopyBoard(currentBoard, boards[0]);
15340         CopyBoard(menuBoard, initialPosition);
15341         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15342             SendToICS(ics_prefix);
15343             SendToICS("bsetup clear\n");
15344         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15345             SendToICS(ics_prefix);
15346             SendToICS("clearboard\n");
15347         } else {
15348             int nonEmpty = 0;
15349             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15350                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15351                 for (y = 0; y < BOARD_HEIGHT; y++) {
15352                     if (gameMode == IcsExamining) {
15353                         if (boards[currentMove][y][x] != EmptySquare) {
15354                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15355                                     AAA + x, ONE + y);
15356                             SendToICS(buf);
15357                         }
15358                     } else if(boards[0][y][x] != DarkSquare) {
15359                         if(boards[0][y][x] != p) nonEmpty++;
15360                         boards[0][y][x] = p;
15361                     }
15362                 }
15363             }
15364             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15365                 int r;
15366                 for(r = 0; r < BOARD_HEIGHT; r++) {
15367                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15368                     ChessSquare p = menuBoard[r][x];
15369                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15370                   }
15371                 }
15372                 DisplayMessage("Clicking clock again restores position", "");
15373                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15374                 if(!nonEmpty) { // asked to clear an empty board
15375                     CopyBoard(boards[0], menuBoard);
15376                 } else
15377                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15378                     CopyBoard(boards[0], initialPosition);
15379                 } else
15380                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15381                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15382                     CopyBoard(boards[0], erasedBoard);
15383                 } else
15384                     CopyBoard(erasedBoard, currentBoard);
15385
15386             }
15387         }
15388         if (gameMode == EditPosition) {
15389             DrawPosition(FALSE, boards[0]);
15390         }
15391         break;
15392
15393       case WhitePlay:
15394         SetWhiteToPlayEvent();
15395         break;
15396
15397       case BlackPlay:
15398         SetBlackToPlayEvent();
15399         break;
15400
15401       case EmptySquare:
15402         if (gameMode == IcsExamining) {
15403             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15404             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15405             SendToICS(buf);
15406         } else {
15407             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15408                 if(x == BOARD_LEFT-2) {
15409                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15410                     boards[0][y][1] = 0;
15411                 } else
15412                 if(x == BOARD_RGHT+1) {
15413                     if(y >= gameInfo.holdingsSize) break;
15414                     boards[0][y][BOARD_WIDTH-2] = 0;
15415                 } else break;
15416             }
15417             boards[0][y][x] = EmptySquare;
15418             DrawPosition(FALSE, boards[0]);
15419         }
15420         break;
15421
15422       case PromotePiece:
15423         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15424            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15425             selection = (ChessSquare) (PROMOTED(piece));
15426         } else if(piece == EmptySquare) selection = WhiteSilver;
15427         else selection = (ChessSquare)((int)piece - 1);
15428         goto defaultlabel;
15429
15430       case DemotePiece:
15431         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15432            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15433             selection = (ChessSquare) (DEMOTED(piece));
15434         } else if(piece == EmptySquare) selection = BlackSilver;
15435         else selection = (ChessSquare)((int)piece + 1);
15436         goto defaultlabel;
15437
15438       case WhiteQueen:
15439       case BlackQueen:
15440         if(gameInfo.variant == VariantShatranj ||
15441            gameInfo.variant == VariantXiangqi  ||
15442            gameInfo.variant == VariantCourier  ||
15443            gameInfo.variant == VariantASEAN    ||
15444            gameInfo.variant == VariantMakruk     )
15445             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15446         goto defaultlabel;
15447
15448       case WhiteKing:
15449       case BlackKing:
15450         if(gameInfo.variant == VariantXiangqi)
15451             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15452         if(gameInfo.variant == VariantKnightmate)
15453             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15454       default:
15455         defaultlabel:
15456         if (gameMode == IcsExamining) {
15457             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15458             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15459                      PieceToChar(selection), AAA + x, ONE + y);
15460             SendToICS(buf);
15461         } else {
15462             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15463                 int n;
15464                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15465                     n = PieceToNumber(selection - BlackPawn);
15466                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15467                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15468                     boards[0][BOARD_HEIGHT-1-n][1]++;
15469                 } else
15470                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15471                     n = PieceToNumber(selection);
15472                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15473                     boards[0][n][BOARD_WIDTH-1] = selection;
15474                     boards[0][n][BOARD_WIDTH-2]++;
15475                 }
15476             } else
15477             boards[0][y][x] = selection;
15478             DrawPosition(TRUE, boards[0]);
15479             ClearHighlights();
15480             fromX = fromY = -1;
15481         }
15482         break;
15483     }
15484 }
15485
15486
15487 void
15488 DropMenuEvent (ChessSquare selection, int x, int y)
15489 {
15490     ChessMove moveType;
15491
15492     switch (gameMode) {
15493       case IcsPlayingWhite:
15494       case MachinePlaysBlack:
15495         if (!WhiteOnMove(currentMove)) {
15496             DisplayMoveError(_("It is Black's turn"));
15497             return;
15498         }
15499         moveType = WhiteDrop;
15500         break;
15501       case IcsPlayingBlack:
15502       case MachinePlaysWhite:
15503         if (WhiteOnMove(currentMove)) {
15504             DisplayMoveError(_("It is White's turn"));
15505             return;
15506         }
15507         moveType = BlackDrop;
15508         break;
15509       case EditGame:
15510         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15511         break;
15512       default:
15513         return;
15514     }
15515
15516     if (moveType == BlackDrop && selection < BlackPawn) {
15517       selection = (ChessSquare) ((int) selection
15518                                  + (int) BlackPawn - (int) WhitePawn);
15519     }
15520     if (boards[currentMove][y][x] != EmptySquare) {
15521         DisplayMoveError(_("That square is occupied"));
15522         return;
15523     }
15524
15525     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15526 }
15527
15528 void
15529 AcceptEvent ()
15530 {
15531     /* Accept a pending offer of any kind from opponent */
15532
15533     if (appData.icsActive) {
15534         SendToICS(ics_prefix);
15535         SendToICS("accept\n");
15536     } else if (cmailMsgLoaded) {
15537         if (currentMove == cmailOldMove &&
15538             commentList[cmailOldMove] != NULL &&
15539             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15540                    "Black offers a draw" : "White offers a draw")) {
15541             TruncateGame();
15542             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15543             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15544         } else {
15545             DisplayError(_("There is no pending offer on this move"), 0);
15546             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15547         }
15548     } else {
15549         /* Not used for offers from chess program */
15550     }
15551 }
15552
15553 void
15554 DeclineEvent ()
15555 {
15556     /* Decline a pending offer of any kind from opponent */
15557
15558     if (appData.icsActive) {
15559         SendToICS(ics_prefix);
15560         SendToICS("decline\n");
15561     } else if (cmailMsgLoaded) {
15562         if (currentMove == cmailOldMove &&
15563             commentList[cmailOldMove] != NULL &&
15564             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15565                    "Black offers a draw" : "White offers a draw")) {
15566 #ifdef NOTDEF
15567             AppendComment(cmailOldMove, "Draw declined", TRUE);
15568             DisplayComment(cmailOldMove - 1, "Draw declined");
15569 #endif /*NOTDEF*/
15570         } else {
15571             DisplayError(_("There is no pending offer on this move"), 0);
15572         }
15573     } else {
15574         /* Not used for offers from chess program */
15575     }
15576 }
15577
15578 void
15579 RematchEvent ()
15580 {
15581     /* Issue ICS rematch command */
15582     if (appData.icsActive) {
15583         SendToICS(ics_prefix);
15584         SendToICS("rematch\n");
15585     }
15586 }
15587
15588 void
15589 CallFlagEvent ()
15590 {
15591     /* Call your opponent's flag (claim a win on time) */
15592     if (appData.icsActive) {
15593         SendToICS(ics_prefix);
15594         SendToICS("flag\n");
15595     } else {
15596         switch (gameMode) {
15597           default:
15598             return;
15599           case MachinePlaysWhite:
15600             if (whiteFlag) {
15601                 if (blackFlag)
15602                   GameEnds(GameIsDrawn, "Both players ran out of time",
15603                            GE_PLAYER);
15604                 else
15605                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15606             } else {
15607                 DisplayError(_("Your opponent is not out of time"), 0);
15608             }
15609             break;
15610           case MachinePlaysBlack:
15611             if (blackFlag) {
15612                 if (whiteFlag)
15613                   GameEnds(GameIsDrawn, "Both players ran out of time",
15614                            GE_PLAYER);
15615                 else
15616                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15617             } else {
15618                 DisplayError(_("Your opponent is not out of time"), 0);
15619             }
15620             break;
15621         }
15622     }
15623 }
15624
15625 void
15626 ClockClick (int which)
15627 {       // [HGM] code moved to back-end from winboard.c
15628         if(which) { // black clock
15629           if (gameMode == EditPosition || gameMode == IcsExamining) {
15630             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15631             SetBlackToPlayEvent();
15632           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15633                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15634           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15635           } else if (shiftKey) {
15636             AdjustClock(which, -1);
15637           } else if (gameMode == IcsPlayingWhite ||
15638                      gameMode == MachinePlaysBlack) {
15639             CallFlagEvent();
15640           }
15641         } else { // white clock
15642           if (gameMode == EditPosition || gameMode == IcsExamining) {
15643             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15644             SetWhiteToPlayEvent();
15645           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15646                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15647           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15648           } else if (shiftKey) {
15649             AdjustClock(which, -1);
15650           } else if (gameMode == IcsPlayingBlack ||
15651                    gameMode == MachinePlaysWhite) {
15652             CallFlagEvent();
15653           }
15654         }
15655 }
15656
15657 void
15658 DrawEvent ()
15659 {
15660     /* Offer draw or accept pending draw offer from opponent */
15661
15662     if (appData.icsActive) {
15663         /* Note: tournament rules require draw offers to be
15664            made after you make your move but before you punch
15665            your clock.  Currently ICS doesn't let you do that;
15666            instead, you immediately punch your clock after making
15667            a move, but you can offer a draw at any time. */
15668
15669         SendToICS(ics_prefix);
15670         SendToICS("draw\n");
15671         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15672     } else if (cmailMsgLoaded) {
15673         if (currentMove == cmailOldMove &&
15674             commentList[cmailOldMove] != NULL &&
15675             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15676                    "Black offers a draw" : "White offers a draw")) {
15677             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15678             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15679         } else if (currentMove == cmailOldMove + 1) {
15680             char *offer = WhiteOnMove(cmailOldMove) ?
15681               "White offers a draw" : "Black offers a draw";
15682             AppendComment(currentMove, offer, TRUE);
15683             DisplayComment(currentMove - 1, offer);
15684             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15685         } else {
15686             DisplayError(_("You must make your move before offering a draw"), 0);
15687             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15688         }
15689     } else if (first.offeredDraw) {
15690         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15691     } else {
15692         if (first.sendDrawOffers) {
15693             SendToProgram("draw\n", &first);
15694             userOfferedDraw = TRUE;
15695         }
15696     }
15697 }
15698
15699 void
15700 AdjournEvent ()
15701 {
15702     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15703
15704     if (appData.icsActive) {
15705         SendToICS(ics_prefix);
15706         SendToICS("adjourn\n");
15707     } else {
15708         /* Currently GNU Chess doesn't offer or accept Adjourns */
15709     }
15710 }
15711
15712
15713 void
15714 AbortEvent ()
15715 {
15716     /* Offer Abort or accept pending Abort offer from opponent */
15717
15718     if (appData.icsActive) {
15719         SendToICS(ics_prefix);
15720         SendToICS("abort\n");
15721     } else {
15722         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15723     }
15724 }
15725
15726 void
15727 ResignEvent ()
15728 {
15729     /* Resign.  You can do this even if it's not your turn. */
15730
15731     if (appData.icsActive) {
15732         SendToICS(ics_prefix);
15733         SendToICS("resign\n");
15734     } else {
15735         switch (gameMode) {
15736           case MachinePlaysWhite:
15737             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15738             break;
15739           case MachinePlaysBlack:
15740             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15741             break;
15742           case EditGame:
15743             if (cmailMsgLoaded) {
15744                 TruncateGame();
15745                 if (WhiteOnMove(cmailOldMove)) {
15746                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15747                 } else {
15748                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15749                 }
15750                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15751             }
15752             break;
15753           default:
15754             break;
15755         }
15756     }
15757 }
15758
15759
15760 void
15761 StopObservingEvent ()
15762 {
15763     /* Stop observing current games */
15764     SendToICS(ics_prefix);
15765     SendToICS("unobserve\n");
15766 }
15767
15768 void
15769 StopExaminingEvent ()
15770 {
15771     /* Stop observing current game */
15772     SendToICS(ics_prefix);
15773     SendToICS("unexamine\n");
15774 }
15775
15776 void
15777 ForwardInner (int target)
15778 {
15779     int limit; int oldSeekGraphUp = seekGraphUp;
15780
15781     if (appData.debugMode)
15782         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15783                 target, currentMove, forwardMostMove);
15784
15785     if (gameMode == EditPosition)
15786       return;
15787
15788     seekGraphUp = FALSE;
15789     MarkTargetSquares(1);
15790     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15791
15792     if (gameMode == PlayFromGameFile && !pausing)
15793       PauseEvent();
15794
15795     if (gameMode == IcsExamining && pausing)
15796       limit = pauseExamForwardMostMove;
15797     else
15798       limit = forwardMostMove;
15799
15800     if (target > limit) target = limit;
15801
15802     if (target > 0 && moveList[target - 1][0]) {
15803         int fromX, fromY, toX, toY;
15804         toX = moveList[target - 1][2] - AAA;
15805         toY = moveList[target - 1][3] - ONE;
15806         if (moveList[target - 1][1] == '@') {
15807             if (appData.highlightLastMove) {
15808                 SetHighlights(-1, -1, toX, toY);
15809             }
15810         } else {
15811             int viaX = moveList[target - 1][5] - AAA;
15812             int viaY = moveList[target - 1][6] - ONE;
15813             fromX = moveList[target - 1][0] - AAA;
15814             fromY = moveList[target - 1][1] - ONE;
15815             if (target == currentMove + 1) {
15816                 if(moveList[target - 1][4] == ';') { // multi-leg
15817                     ChessSquare piece = boards[currentMove][viaY][viaX];
15818                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15819                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15820                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15821                     boards[currentMove][viaY][viaX] = piece;
15822                 } else
15823                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15824             }
15825             if (appData.highlightLastMove) {
15826                 SetHighlights(fromX, fromY, toX, toY);
15827             }
15828         }
15829     }
15830     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15831         gameMode == Training || gameMode == PlayFromGameFile ||
15832         gameMode == AnalyzeFile) {
15833         while (currentMove < target) {
15834             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15835             SendMoveToProgram(currentMove++, &first);
15836         }
15837     } else {
15838         currentMove = target;
15839     }
15840
15841     if (gameMode == EditGame || gameMode == EndOfGame) {
15842         whiteTimeRemaining = timeRemaining[0][currentMove];
15843         blackTimeRemaining = timeRemaining[1][currentMove];
15844     }
15845     DisplayBothClocks();
15846     DisplayMove(currentMove - 1);
15847     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15848     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15849     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15850         DisplayComment(currentMove - 1, commentList[currentMove]);
15851     }
15852     ClearMap(); // [HGM] exclude: invalidate map
15853 }
15854
15855
15856 void
15857 ForwardEvent ()
15858 {
15859     if (gameMode == IcsExamining && !pausing) {
15860         SendToICS(ics_prefix);
15861         SendToICS("forward\n");
15862     } else {
15863         ForwardInner(currentMove + 1);
15864     }
15865 }
15866
15867 void
15868 ToEndEvent ()
15869 {
15870     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15871         /* to optimze, we temporarily turn off analysis mode while we feed
15872          * the remaining moves to the engine. Otherwise we get analysis output
15873          * after each move.
15874          */
15875         if (first.analysisSupport) {
15876           SendToProgram("exit\nforce\n", &first);
15877           first.analyzing = FALSE;
15878         }
15879     }
15880
15881     if (gameMode == IcsExamining && !pausing) {
15882         SendToICS(ics_prefix);
15883         SendToICS("forward 999999\n");
15884     } else {
15885         ForwardInner(forwardMostMove);
15886     }
15887
15888     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15889         /* we have fed all the moves, so reactivate analysis mode */
15890         SendToProgram("analyze\n", &first);
15891         first.analyzing = TRUE;
15892         /*first.maybeThinking = TRUE;*/
15893         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15894     }
15895 }
15896
15897 void
15898 BackwardInner (int target)
15899 {
15900     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15901
15902     if (appData.debugMode)
15903         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15904                 target, currentMove, forwardMostMove);
15905
15906     if (gameMode == EditPosition) return;
15907     seekGraphUp = FALSE;
15908     MarkTargetSquares(1);
15909     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15910     if (currentMove <= backwardMostMove) {
15911         ClearHighlights();
15912         DrawPosition(full_redraw, boards[currentMove]);
15913         return;
15914     }
15915     if (gameMode == PlayFromGameFile && !pausing)
15916       PauseEvent();
15917
15918     if (moveList[target][0]) {
15919         int fromX, fromY, toX, toY;
15920         toX = moveList[target][2] - AAA;
15921         toY = moveList[target][3] - ONE;
15922         if (moveList[target][1] == '@') {
15923             if (appData.highlightLastMove) {
15924                 SetHighlights(-1, -1, toX, toY);
15925             }
15926         } else {
15927             fromX = moveList[target][0] - AAA;
15928             fromY = moveList[target][1] - ONE;
15929             if (target == currentMove - 1) {
15930                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15931             }
15932             if (appData.highlightLastMove) {
15933                 SetHighlights(fromX, fromY, toX, toY);
15934             }
15935         }
15936     }
15937     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15938         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15939         while (currentMove > target) {
15940             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15941                 // null move cannot be undone. Reload program with move history before it.
15942                 int i;
15943                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15944                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15945                 }
15946                 SendBoard(&first, i);
15947               if(second.analyzing) SendBoard(&second, i);
15948                 for(currentMove=i; currentMove<target; currentMove++) {
15949                     SendMoveToProgram(currentMove, &first);
15950                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15951                 }
15952                 break;
15953             }
15954             SendToBoth("undo\n");
15955             currentMove--;
15956         }
15957     } else {
15958         currentMove = target;
15959     }
15960
15961     if (gameMode == EditGame || gameMode == EndOfGame) {
15962         whiteTimeRemaining = timeRemaining[0][currentMove];
15963         blackTimeRemaining = timeRemaining[1][currentMove];
15964     }
15965     DisplayBothClocks();
15966     DisplayMove(currentMove - 1);
15967     DrawPosition(full_redraw, boards[currentMove]);
15968     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15969     // [HGM] PV info: routine tests if comment empty
15970     DisplayComment(currentMove - 1, commentList[currentMove]);
15971     ClearMap(); // [HGM] exclude: invalidate map
15972 }
15973
15974 void
15975 BackwardEvent ()
15976 {
15977     if (gameMode == IcsExamining && !pausing) {
15978         SendToICS(ics_prefix);
15979         SendToICS("backward\n");
15980     } else {
15981         BackwardInner(currentMove - 1);
15982     }
15983 }
15984
15985 void
15986 ToStartEvent ()
15987 {
15988     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15989         /* to optimize, we temporarily turn off analysis mode while we undo
15990          * all the moves. Otherwise we get analysis output after each undo.
15991          */
15992         if (first.analysisSupport) {
15993           SendToProgram("exit\nforce\n", &first);
15994           first.analyzing = FALSE;
15995         }
15996     }
15997
15998     if (gameMode == IcsExamining && !pausing) {
15999         SendToICS(ics_prefix);
16000         SendToICS("backward 999999\n");
16001     } else {
16002         BackwardInner(backwardMostMove);
16003     }
16004
16005     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16006         /* we have fed all the moves, so reactivate analysis mode */
16007         SendToProgram("analyze\n", &first);
16008         first.analyzing = TRUE;
16009         /*first.maybeThinking = TRUE;*/
16010         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16011     }
16012 }
16013
16014 void
16015 ToNrEvent (int to)
16016 {
16017   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16018   if (to >= forwardMostMove) to = forwardMostMove;
16019   if (to <= backwardMostMove) to = backwardMostMove;
16020   if (to < currentMove) {
16021     BackwardInner(to);
16022   } else {
16023     ForwardInner(to);
16024   }
16025 }
16026
16027 void
16028 RevertEvent (Boolean annotate)
16029 {
16030     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16031         return;
16032     }
16033     if (gameMode != IcsExamining) {
16034         DisplayError(_("You are not examining a game"), 0);
16035         return;
16036     }
16037     if (pausing) {
16038         DisplayError(_("You can't revert while pausing"), 0);
16039         return;
16040     }
16041     SendToICS(ics_prefix);
16042     SendToICS("revert\n");
16043 }
16044
16045 void
16046 RetractMoveEvent ()
16047 {
16048     switch (gameMode) {
16049       case MachinePlaysWhite:
16050       case MachinePlaysBlack:
16051         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16052             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16053             return;
16054         }
16055         if (forwardMostMove < 2) return;
16056         currentMove = forwardMostMove = forwardMostMove - 2;
16057         whiteTimeRemaining = timeRemaining[0][currentMove];
16058         blackTimeRemaining = timeRemaining[1][currentMove];
16059         DisplayBothClocks();
16060         DisplayMove(currentMove - 1);
16061         ClearHighlights();/*!! could figure this out*/
16062         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16063         SendToProgram("remove\n", &first);
16064         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16065         break;
16066
16067       case BeginningOfGame:
16068       default:
16069         break;
16070
16071       case IcsPlayingWhite:
16072       case IcsPlayingBlack:
16073         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16074             SendToICS(ics_prefix);
16075             SendToICS("takeback 2\n");
16076         } else {
16077             SendToICS(ics_prefix);
16078             SendToICS("takeback 1\n");
16079         }
16080         break;
16081     }
16082 }
16083
16084 void
16085 MoveNowEvent ()
16086 {
16087     ChessProgramState *cps;
16088
16089     switch (gameMode) {
16090       case MachinePlaysWhite:
16091         if (!WhiteOnMove(forwardMostMove)) {
16092             DisplayError(_("It is your turn"), 0);
16093             return;
16094         }
16095         cps = &first;
16096         break;
16097       case MachinePlaysBlack:
16098         if (WhiteOnMove(forwardMostMove)) {
16099             DisplayError(_("It is your turn"), 0);
16100             return;
16101         }
16102         cps = &first;
16103         break;
16104       case TwoMachinesPlay:
16105         if (WhiteOnMove(forwardMostMove) ==
16106             (first.twoMachinesColor[0] == 'w')) {
16107             cps = &first;
16108         } else {
16109             cps = &second;
16110         }
16111         break;
16112       case BeginningOfGame:
16113       default:
16114         return;
16115     }
16116     SendToProgram("?\n", cps);
16117 }
16118
16119 void
16120 TruncateGameEvent ()
16121 {
16122     EditGameEvent();
16123     if (gameMode != EditGame) return;
16124     TruncateGame();
16125 }
16126
16127 void
16128 TruncateGame ()
16129 {
16130     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16131     if (forwardMostMove > currentMove) {
16132         if (gameInfo.resultDetails != NULL) {
16133             free(gameInfo.resultDetails);
16134             gameInfo.resultDetails = NULL;
16135             gameInfo.result = GameUnfinished;
16136         }
16137         forwardMostMove = currentMove;
16138         HistorySet(parseList, backwardMostMove, forwardMostMove,
16139                    currentMove-1);
16140     }
16141 }
16142
16143 void
16144 HintEvent ()
16145 {
16146     if (appData.noChessProgram) return;
16147     switch (gameMode) {
16148       case MachinePlaysWhite:
16149         if (WhiteOnMove(forwardMostMove)) {
16150             DisplayError(_("Wait until your turn."), 0);
16151             return;
16152         }
16153         break;
16154       case BeginningOfGame:
16155       case MachinePlaysBlack:
16156         if (!WhiteOnMove(forwardMostMove)) {
16157             DisplayError(_("Wait until your turn."), 0);
16158             return;
16159         }
16160         break;
16161       default:
16162         DisplayError(_("No hint available"), 0);
16163         return;
16164     }
16165     SendToProgram("hint\n", &first);
16166     hintRequested = TRUE;
16167 }
16168
16169 int
16170 SaveSelected (FILE *g, int dummy, char *dummy2)
16171 {
16172     ListGame * lg = (ListGame *) gameList.head;
16173     int nItem, cnt=0;
16174     FILE *f;
16175
16176     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16177         DisplayError(_("Game list not loaded or empty"), 0);
16178         return 0;
16179     }
16180
16181     creatingBook = TRUE; // suppresses stuff during load game
16182
16183     /* Get list size */
16184     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16185         if(lg->position >= 0) { // selected?
16186             LoadGame(f, nItem, "", TRUE);
16187             SaveGamePGN2(g); // leaves g open
16188             cnt++; DoEvents();
16189         }
16190         lg = (ListGame *) lg->node.succ;
16191     }
16192
16193     fclose(g);
16194     creatingBook = FALSE;
16195
16196     return cnt;
16197 }
16198
16199 void
16200 CreateBookEvent ()
16201 {
16202     ListGame * lg = (ListGame *) gameList.head;
16203     FILE *f, *g;
16204     int nItem;
16205     static int secondTime = FALSE;
16206
16207     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16208         DisplayError(_("Game list not loaded or empty"), 0);
16209         return;
16210     }
16211
16212     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16213         fclose(g);
16214         secondTime++;
16215         DisplayNote(_("Book file exists! Try again for overwrite."));
16216         return;
16217     }
16218
16219     creatingBook = TRUE;
16220     secondTime = FALSE;
16221
16222     /* Get list size */
16223     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16224         if(lg->position >= 0) {
16225             LoadGame(f, nItem, "", TRUE);
16226             AddGameToBook(TRUE);
16227             DoEvents();
16228         }
16229         lg = (ListGame *) lg->node.succ;
16230     }
16231
16232     creatingBook = FALSE;
16233     FlushBook();
16234 }
16235
16236 void
16237 BookEvent ()
16238 {
16239     if (appData.noChessProgram) return;
16240     switch (gameMode) {
16241       case MachinePlaysWhite:
16242         if (WhiteOnMove(forwardMostMove)) {
16243             DisplayError(_("Wait until your turn."), 0);
16244             return;
16245         }
16246         break;
16247       case BeginningOfGame:
16248       case MachinePlaysBlack:
16249         if (!WhiteOnMove(forwardMostMove)) {
16250             DisplayError(_("Wait until your turn."), 0);
16251             return;
16252         }
16253         break;
16254       case EditPosition:
16255         EditPositionDone(TRUE);
16256         break;
16257       case TwoMachinesPlay:
16258         return;
16259       default:
16260         break;
16261     }
16262     SendToProgram("bk\n", &first);
16263     bookOutput[0] = NULLCHAR;
16264     bookRequested = TRUE;
16265 }
16266
16267 void
16268 AboutGameEvent ()
16269 {
16270     char *tags = PGNTags(&gameInfo);
16271     TagsPopUp(tags, CmailMsg());
16272     free(tags);
16273 }
16274
16275 /* end button procedures */
16276
16277 void
16278 PrintPosition (FILE *fp, int move)
16279 {
16280     int i, j;
16281
16282     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16283         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16284             char c = PieceToChar(boards[move][i][j]);
16285             fputc(c == 'x' ? '.' : c, fp);
16286             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16287         }
16288     }
16289     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16290       fprintf(fp, "white to play\n");
16291     else
16292       fprintf(fp, "black to play\n");
16293 }
16294
16295 void
16296 PrintOpponents (FILE *fp)
16297 {
16298     if (gameInfo.white != NULL) {
16299         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16300     } else {
16301         fprintf(fp, "\n");
16302     }
16303 }
16304
16305 /* Find last component of program's own name, using some heuristics */
16306 void
16307 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16308 {
16309     char *p, *q, c;
16310     int local = (strcmp(host, "localhost") == 0);
16311     while (!local && (p = strchr(prog, ';')) != NULL) {
16312         p++;
16313         while (*p == ' ') p++;
16314         prog = p;
16315     }
16316     if (*prog == '"' || *prog == '\'') {
16317         q = strchr(prog + 1, *prog);
16318     } else {
16319         q = strchr(prog, ' ');
16320     }
16321     if (q == NULL) q = prog + strlen(prog);
16322     p = q;
16323     while (p >= prog && *p != '/' && *p != '\\') p--;
16324     p++;
16325     if(p == prog && *p == '"') p++;
16326     c = *q; *q = 0;
16327     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16328     memcpy(buf, p, q - p);
16329     buf[q - p] = NULLCHAR;
16330     if (!local) {
16331         strcat(buf, "@");
16332         strcat(buf, host);
16333     }
16334 }
16335
16336 char *
16337 TimeControlTagValue ()
16338 {
16339     char buf[MSG_SIZ];
16340     if (!appData.clockMode) {
16341       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16342     } else if (movesPerSession > 0) {
16343       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16344     } else if (timeIncrement == 0) {
16345       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16346     } else {
16347       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16348     }
16349     return StrSave(buf);
16350 }
16351
16352 void
16353 SetGameInfo ()
16354 {
16355     /* This routine is used only for certain modes */
16356     VariantClass v = gameInfo.variant;
16357     ChessMove r = GameUnfinished;
16358     char *p = NULL;
16359
16360     if(keepInfo) return;
16361
16362     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16363         r = gameInfo.result;
16364         p = gameInfo.resultDetails;
16365         gameInfo.resultDetails = NULL;
16366     }
16367     ClearGameInfo(&gameInfo);
16368     gameInfo.variant = v;
16369
16370     switch (gameMode) {
16371       case MachinePlaysWhite:
16372         gameInfo.event = StrSave( appData.pgnEventHeader );
16373         gameInfo.site = StrSave(HostName());
16374         gameInfo.date = PGNDate();
16375         gameInfo.round = StrSave("-");
16376         gameInfo.white = StrSave(first.tidy);
16377         gameInfo.black = StrSave(UserName());
16378         gameInfo.timeControl = TimeControlTagValue();
16379         break;
16380
16381       case MachinePlaysBlack:
16382         gameInfo.event = StrSave( appData.pgnEventHeader );
16383         gameInfo.site = StrSave(HostName());
16384         gameInfo.date = PGNDate();
16385         gameInfo.round = StrSave("-");
16386         gameInfo.white = StrSave(UserName());
16387         gameInfo.black = StrSave(first.tidy);
16388         gameInfo.timeControl = TimeControlTagValue();
16389         break;
16390
16391       case TwoMachinesPlay:
16392         gameInfo.event = StrSave( appData.pgnEventHeader );
16393         gameInfo.site = StrSave(HostName());
16394         gameInfo.date = PGNDate();
16395         if (roundNr > 0) {
16396             char buf[MSG_SIZ];
16397             snprintf(buf, MSG_SIZ, "%d", roundNr);
16398             gameInfo.round = StrSave(buf);
16399         } else {
16400             gameInfo.round = StrSave("-");
16401         }
16402         if (first.twoMachinesColor[0] == 'w') {
16403             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16404             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16405         } else {
16406             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16407             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16408         }
16409         gameInfo.timeControl = TimeControlTagValue();
16410         break;
16411
16412       case EditGame:
16413         gameInfo.event = StrSave("Edited game");
16414         gameInfo.site = StrSave(HostName());
16415         gameInfo.date = PGNDate();
16416         gameInfo.round = StrSave("-");
16417         gameInfo.white = StrSave("-");
16418         gameInfo.black = StrSave("-");
16419         gameInfo.result = r;
16420         gameInfo.resultDetails = p;
16421         break;
16422
16423       case EditPosition:
16424         gameInfo.event = StrSave("Edited position");
16425         gameInfo.site = StrSave(HostName());
16426         gameInfo.date = PGNDate();
16427         gameInfo.round = StrSave("-");
16428         gameInfo.white = StrSave("-");
16429         gameInfo.black = StrSave("-");
16430         break;
16431
16432       case IcsPlayingWhite:
16433       case IcsPlayingBlack:
16434       case IcsObserving:
16435       case IcsExamining:
16436         break;
16437
16438       case PlayFromGameFile:
16439         gameInfo.event = StrSave("Game from non-PGN file");
16440         gameInfo.site = StrSave(HostName());
16441         gameInfo.date = PGNDate();
16442         gameInfo.round = StrSave("-");
16443         gameInfo.white = StrSave("?");
16444         gameInfo.black = StrSave("?");
16445         break;
16446
16447       default:
16448         break;
16449     }
16450 }
16451
16452 void
16453 ReplaceComment (int index, char *text)
16454 {
16455     int len;
16456     char *p;
16457     float score;
16458
16459     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16460        pvInfoList[index-1].depth == len &&
16461        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16462        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16463     while (*text == '\n') text++;
16464     len = strlen(text);
16465     while (len > 0 && text[len - 1] == '\n') len--;
16466
16467     if (commentList[index] != NULL)
16468       free(commentList[index]);
16469
16470     if (len == 0) {
16471         commentList[index] = NULL;
16472         return;
16473     }
16474   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16475       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16476       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16477     commentList[index] = (char *) malloc(len + 2);
16478     strncpy(commentList[index], text, len);
16479     commentList[index][len] = '\n';
16480     commentList[index][len + 1] = NULLCHAR;
16481   } else {
16482     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16483     char *p;
16484     commentList[index] = (char *) malloc(len + 7);
16485     safeStrCpy(commentList[index], "{\n", 3);
16486     safeStrCpy(commentList[index]+2, text, len+1);
16487     commentList[index][len+2] = NULLCHAR;
16488     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16489     strcat(commentList[index], "\n}\n");
16490   }
16491 }
16492
16493 void
16494 CrushCRs (char *text)
16495 {
16496   char *p = text;
16497   char *q = text;
16498   char ch;
16499
16500   do {
16501     ch = *p++;
16502     if (ch == '\r') continue;
16503     *q++ = ch;
16504   } while (ch != '\0');
16505 }
16506
16507 void
16508 AppendComment (int index, char *text, Boolean addBraces)
16509 /* addBraces  tells if we should add {} */
16510 {
16511     int oldlen, len;
16512     char *old;
16513
16514 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16515     if(addBraces == 3) addBraces = 0; else // force appending literally
16516     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16517
16518     CrushCRs(text);
16519     while (*text == '\n') text++;
16520     len = strlen(text);
16521     while (len > 0 && text[len - 1] == '\n') len--;
16522     text[len] = NULLCHAR;
16523
16524     if (len == 0) return;
16525
16526     if (commentList[index] != NULL) {
16527       Boolean addClosingBrace = addBraces;
16528         old = commentList[index];
16529         oldlen = strlen(old);
16530         while(commentList[index][oldlen-1] ==  '\n')
16531           commentList[index][--oldlen] = NULLCHAR;
16532         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16533         safeStrCpy(commentList[index], old, oldlen + len + 6);
16534         free(old);
16535         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16536         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16537           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16538           while (*text == '\n') { text++; len--; }
16539           commentList[index][--oldlen] = NULLCHAR;
16540       }
16541         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16542         else          strcat(commentList[index], "\n");
16543         strcat(commentList[index], text);
16544         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16545         else          strcat(commentList[index], "\n");
16546     } else {
16547         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16548         if(addBraces)
16549           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16550         else commentList[index][0] = NULLCHAR;
16551         strcat(commentList[index], text);
16552         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16553         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16554     }
16555 }
16556
16557 static char *
16558 FindStr (char * text, char * sub_text)
16559 {
16560     char * result = strstr( text, sub_text );
16561
16562     if( result != NULL ) {
16563         result += strlen( sub_text );
16564     }
16565
16566     return result;
16567 }
16568
16569 /* [AS] Try to extract PV info from PGN comment */
16570 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16571 char *
16572 GetInfoFromComment (int index, char * text)
16573 {
16574     char * sep = text, *p;
16575
16576     if( text != NULL && index > 0 ) {
16577         int score = 0;
16578         int depth = 0;
16579         int time = -1, sec = 0, deci;
16580         char * s_eval = FindStr( text, "[%eval " );
16581         char * s_emt = FindStr( text, "[%emt " );
16582 #if 0
16583         if( s_eval != NULL || s_emt != NULL ) {
16584 #else
16585         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16586 #endif
16587             /* New style */
16588             char delim;
16589
16590             if( s_eval != NULL ) {
16591                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16592                     return text;
16593                 }
16594
16595                 if( delim != ']' ) {
16596                     return text;
16597                 }
16598             }
16599
16600             if( s_emt != NULL ) {
16601             }
16602                 return text;
16603         }
16604         else {
16605             /* We expect something like: [+|-]nnn.nn/dd */
16606             int score_lo = 0;
16607
16608             if(*text != '{') return text; // [HGM] braces: must be normal comment
16609
16610             sep = strchr( text, '/' );
16611             if( sep == NULL || sep < (text+4) ) {
16612                 return text;
16613             }
16614
16615             p = text;
16616             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16617             if(p[1] == '(') { // comment starts with PV
16618                p = strchr(p, ')'); // locate end of PV
16619                if(p == NULL || sep < p+5) return text;
16620                // at this point we have something like "{(.*) +0.23/6 ..."
16621                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16622                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16623                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16624             }
16625             time = -1; sec = -1; deci = -1;
16626             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16627                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16628                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16629                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16630                 return text;
16631             }
16632
16633             if( score_lo < 0 || score_lo >= 100 ) {
16634                 return text;
16635             }
16636
16637             if(sec >= 0) time = 600*time + 10*sec; else
16638             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16639
16640             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16641
16642             /* [HGM] PV time: now locate end of PV info */
16643             while( *++sep >= '0' && *sep <= '9'); // strip depth
16644             if(time >= 0)
16645             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16646             if(sec >= 0)
16647             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16648             if(deci >= 0)
16649             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16650             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16651         }
16652
16653         if( depth <= 0 ) {
16654             return text;
16655         }
16656
16657         if( time < 0 ) {
16658             time = -1;
16659         }
16660
16661         pvInfoList[index-1].depth = depth;
16662         pvInfoList[index-1].score = score;
16663         pvInfoList[index-1].time  = 10*time; // centi-sec
16664         if(*sep == '}') *sep = 0; else *--sep = '{';
16665         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16666     }
16667     return sep;
16668 }
16669
16670 void
16671 SendToProgram (char *message, ChessProgramState *cps)
16672 {
16673     int count, outCount, error;
16674     char buf[MSG_SIZ];
16675
16676     if (cps->pr == NoProc) return;
16677     Attention(cps);
16678
16679     if (appData.debugMode) {
16680         TimeMark now;
16681         GetTimeMark(&now);
16682         fprintf(debugFP, "%ld >%-6s: %s",
16683                 SubtractTimeMarks(&now, &programStartTime),
16684                 cps->which, message);
16685         if(serverFP)
16686             fprintf(serverFP, "%ld >%-6s: %s",
16687                 SubtractTimeMarks(&now, &programStartTime),
16688                 cps->which, message), fflush(serverFP);
16689     }
16690
16691     count = strlen(message);
16692     outCount = OutputToProcess(cps->pr, message, count, &error);
16693     if (outCount < count && !exiting
16694                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16695       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16696       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16697         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16698             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16699                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16700                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16701                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16702             } else {
16703                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16704                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16705                 gameInfo.result = res;
16706             }
16707             gameInfo.resultDetails = StrSave(buf);
16708         }
16709         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16710         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16711     }
16712 }
16713
16714 void
16715 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16716 {
16717     char *end_str;
16718     char buf[MSG_SIZ];
16719     ChessProgramState *cps = (ChessProgramState *)closure;
16720
16721     if (isr != cps->isr) return; /* Killed intentionally */
16722     if (count <= 0) {
16723         if (count == 0) {
16724             RemoveInputSource(cps->isr);
16725             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16726                     _(cps->which), cps->program);
16727             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16728             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16729                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16730                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16731                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16732                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16733                 } else {
16734                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16735                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16736                     gameInfo.result = res;
16737                 }
16738                 gameInfo.resultDetails = StrSave(buf);
16739             }
16740             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16741             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16742         } else {
16743             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16744                     _(cps->which), cps->program);
16745             RemoveInputSource(cps->isr);
16746
16747             /* [AS] Program is misbehaving badly... kill it */
16748             if( count == -2 ) {
16749                 DestroyChildProcess( cps->pr, 9 );
16750                 cps->pr = NoProc;
16751             }
16752
16753             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16754         }
16755         return;
16756     }
16757
16758     if ((end_str = strchr(message, '\r')) != NULL)
16759       *end_str = NULLCHAR;
16760     if ((end_str = strchr(message, '\n')) != NULL)
16761       *end_str = NULLCHAR;
16762
16763     if (appData.debugMode) {
16764         TimeMark now; int print = 1;
16765         char *quote = ""; char c; int i;
16766
16767         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16768                 char start = message[0];
16769                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16770                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16771                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16772                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16773                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16774                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16775                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16776                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16777                    sscanf(message, "hint: %c", &c)!=1 &&
16778                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16779                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16780                     print = (appData.engineComments >= 2);
16781                 }
16782                 message[0] = start; // restore original message
16783         }
16784         if(print) {
16785                 GetTimeMark(&now);
16786                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16787                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16788                         quote,
16789                         message);
16790                 if(serverFP)
16791                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16792                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16793                         quote,
16794                         message), fflush(serverFP);
16795         }
16796     }
16797
16798     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16799     if (appData.icsEngineAnalyze) {
16800         if (strstr(message, "whisper") != NULL ||
16801              strstr(message, "kibitz") != NULL ||
16802             strstr(message, "tellics") != NULL) return;
16803     }
16804
16805     HandleMachineMove(message, cps);
16806 }
16807
16808
16809 void
16810 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16811 {
16812     char buf[MSG_SIZ];
16813     int seconds;
16814
16815     if( timeControl_2 > 0 ) {
16816         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16817             tc = timeControl_2;
16818         }
16819     }
16820     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16821     inc /= cps->timeOdds;
16822     st  /= cps->timeOdds;
16823
16824     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16825
16826     if (st > 0) {
16827       /* Set exact time per move, normally using st command */
16828       if (cps->stKludge) {
16829         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16830         seconds = st % 60;
16831         if (seconds == 0) {
16832           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16833         } else {
16834           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16835         }
16836       } else {
16837         snprintf(buf, MSG_SIZ, "st %d\n", st);
16838       }
16839     } else {
16840       /* Set conventional or incremental time control, using level command */
16841       if (seconds == 0) {
16842         /* Note old gnuchess bug -- minutes:seconds used to not work.
16843            Fixed in later versions, but still avoid :seconds
16844            when seconds is 0. */
16845         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16846       } else {
16847         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16848                  seconds, inc/1000.);
16849       }
16850     }
16851     SendToProgram(buf, cps);
16852
16853     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16854     /* Orthogonally, limit search to given depth */
16855     if (sd > 0) {
16856       if (cps->sdKludge) {
16857         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16858       } else {
16859         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16860       }
16861       SendToProgram(buf, cps);
16862     }
16863
16864     if(cps->nps >= 0) { /* [HGM] nps */
16865         if(cps->supportsNPS == FALSE)
16866           cps->nps = -1; // don't use if engine explicitly says not supported!
16867         else {
16868           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16869           SendToProgram(buf, cps);
16870         }
16871     }
16872 }
16873
16874 ChessProgramState *
16875 WhitePlayer ()
16876 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16877 {
16878     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16879        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16880         return &second;
16881     return &first;
16882 }
16883
16884 void
16885 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16886 {
16887     char message[MSG_SIZ];
16888     long time, otime;
16889
16890     /* Note: this routine must be called when the clocks are stopped
16891        or when they have *just* been set or switched; otherwise
16892        it will be off by the time since the current tick started.
16893     */
16894     if (machineWhite) {
16895         time = whiteTimeRemaining / 10;
16896         otime = blackTimeRemaining / 10;
16897     } else {
16898         time = blackTimeRemaining / 10;
16899         otime = whiteTimeRemaining / 10;
16900     }
16901     /* [HGM] translate opponent's time by time-odds factor */
16902     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16903
16904     if (time <= 0) time = 1;
16905     if (otime <= 0) otime = 1;
16906
16907     snprintf(message, MSG_SIZ, "time %ld\n", time);
16908     SendToProgram(message, cps);
16909
16910     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16911     SendToProgram(message, cps);
16912 }
16913
16914 char *
16915 EngineDefinedVariant (ChessProgramState *cps, int n)
16916 {   // return name of n-th unknown variant that engine supports
16917     static char buf[MSG_SIZ];
16918     char *p, *s = cps->variants;
16919     if(!s) return NULL;
16920     do { // parse string from variants feature
16921       VariantClass v;
16922         p = strchr(s, ',');
16923         if(p) *p = NULLCHAR;
16924       v = StringToVariant(s);
16925       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16926         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16927             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16928                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16929                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16930                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16931             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16932         }
16933         if(p) *p++ = ',';
16934         if(n < 0) return buf;
16935     } while(s = p);
16936     return NULL;
16937 }
16938
16939 int
16940 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16941 {
16942   char buf[MSG_SIZ];
16943   int len = strlen(name);
16944   int val;
16945
16946   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16947     (*p) += len + 1;
16948     sscanf(*p, "%d", &val);
16949     *loc = (val != 0);
16950     while (**p && **p != ' ')
16951       (*p)++;
16952     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16953     SendToProgram(buf, cps);
16954     return TRUE;
16955   }
16956   return FALSE;
16957 }
16958
16959 int
16960 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16961 {
16962   char buf[MSG_SIZ];
16963   int len = strlen(name);
16964   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16965     (*p) += len + 1;
16966     sscanf(*p, "%d", loc);
16967     while (**p && **p != ' ') (*p)++;
16968     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16969     SendToProgram(buf, cps);
16970     return TRUE;
16971   }
16972   return FALSE;
16973 }
16974
16975 int
16976 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16977 {
16978   char buf[MSG_SIZ];
16979   int len = strlen(name);
16980   if (strncmp((*p), name, len) == 0
16981       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16982     (*p) += len + 2;
16983     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16984     sscanf(*p, "%[^\"]", *loc);
16985     while (**p && **p != '\"') (*p)++;
16986     if (**p == '\"') (*p)++;
16987     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16988     SendToProgram(buf, cps);
16989     return TRUE;
16990   }
16991   return FALSE;
16992 }
16993
16994 int
16995 ParseOption (Option *opt, ChessProgramState *cps)
16996 // [HGM] options: process the string that defines an engine option, and determine
16997 // name, type, default value, and allowed value range
16998 {
16999         char *p, *q, buf[MSG_SIZ];
17000         int n, min = (-1)<<31, max = 1<<31, def;
17001
17002         opt->target = &opt->value;   // OK for spin/slider and checkbox
17003         if(p = strstr(opt->name, " -spin ")) {
17004             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17005             if(max < min) max = min; // enforce consistency
17006             if(def < min) def = min;
17007             if(def > max) def = max;
17008             opt->value = def;
17009             opt->min = min;
17010             opt->max = max;
17011             opt->type = Spin;
17012         } else if((p = strstr(opt->name, " -slider "))) {
17013             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17014             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17015             if(max < min) max = min; // enforce consistency
17016             if(def < min) def = min;
17017             if(def > max) def = max;
17018             opt->value = def;
17019             opt->min = min;
17020             opt->max = max;
17021             opt->type = Spin; // Slider;
17022         } else if((p = strstr(opt->name, " -string "))) {
17023             opt->textValue = p+9;
17024             opt->type = TextBox;
17025             opt->target = &opt->textValue;
17026         } else if((p = strstr(opt->name, " -file "))) {
17027             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17028             opt->target = opt->textValue = p+7;
17029             opt->type = FileName; // FileName;
17030             opt->target = &opt->textValue;
17031         } else if((p = strstr(opt->name, " -path "))) {
17032             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17033             opt->target = opt->textValue = p+7;
17034             opt->type = PathName; // PathName;
17035             opt->target = &opt->textValue;
17036         } else if(p = strstr(opt->name, " -check ")) {
17037             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17038             opt->value = (def != 0);
17039             opt->type = CheckBox;
17040         } else if(p = strstr(opt->name, " -combo ")) {
17041             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17042             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17043             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17044             opt->value = n = 0;
17045             while(q = StrStr(q, " /// ")) {
17046                 n++; *q = 0;    // count choices, and null-terminate each of them
17047                 q += 5;
17048                 if(*q == '*') { // remember default, which is marked with * prefix
17049                     q++;
17050                     opt->value = n;
17051                 }
17052                 cps->comboList[cps->comboCnt++] = q;
17053             }
17054             cps->comboList[cps->comboCnt++] = NULL;
17055             opt->max = n + 1;
17056             opt->type = ComboBox;
17057         } else if(p = strstr(opt->name, " -button")) {
17058             opt->type = Button;
17059         } else if(p = strstr(opt->name, " -save")) {
17060             opt->type = SaveButton;
17061         } else return FALSE;
17062         *p = 0; // terminate option name
17063         // now look if the command-line options define a setting for this engine option.
17064         if(cps->optionSettings && cps->optionSettings[0])
17065             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17066         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17067           snprintf(buf, MSG_SIZ, "option %s", p);
17068                 if(p = strstr(buf, ",")) *p = 0;
17069                 if(q = strchr(buf, '=')) switch(opt->type) {
17070                     case ComboBox:
17071                         for(n=0; n<opt->max; n++)
17072                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17073                         break;
17074                     case TextBox:
17075                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17076                         break;
17077                     case Spin:
17078                     case CheckBox:
17079                         opt->value = atoi(q+1);
17080                     default:
17081                         break;
17082                 }
17083                 strcat(buf, "\n");
17084                 SendToProgram(buf, cps);
17085         }
17086         return TRUE;
17087 }
17088
17089 void
17090 FeatureDone (ChessProgramState *cps, int val)
17091 {
17092   DelayedEventCallback cb = GetDelayedEvent();
17093   if ((cb == InitBackEnd3 && cps == &first) ||
17094       (cb == SettingsMenuIfReady && cps == &second) ||
17095       (cb == LoadEngine) ||
17096       (cb == TwoMachinesEventIfReady)) {
17097     CancelDelayedEvent();
17098     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17099   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17100   cps->initDone = val;
17101   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17102 }
17103
17104 /* Parse feature command from engine */
17105 void
17106 ParseFeatures (char *args, ChessProgramState *cps)
17107 {
17108   char *p = args;
17109   char *q = NULL;
17110   int val;
17111   char buf[MSG_SIZ];
17112
17113   for (;;) {
17114     while (*p == ' ') p++;
17115     if (*p == NULLCHAR) return;
17116
17117     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17118     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17119     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17120     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17121     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17122     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17123     if (BoolFeature(&p, "reuse", &val, cps)) {
17124       /* Engine can disable reuse, but can't enable it if user said no */
17125       if (!val) cps->reuse = FALSE;
17126       continue;
17127     }
17128     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17129     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17130       if (gameMode == TwoMachinesPlay) {
17131         DisplayTwoMachinesTitle();
17132       } else {
17133         DisplayTitle("");
17134       }
17135       continue;
17136     }
17137     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17138     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17139     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17140     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17141     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17142     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17143     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17144     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17145     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17146     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17147     if (IntFeature(&p, "done", &val, cps)) {
17148       FeatureDone(cps, val);
17149       continue;
17150     }
17151     /* Added by Tord: */
17152     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17153     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17154     /* End of additions by Tord */
17155
17156     /* [HGM] added features: */
17157     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17158     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17159     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17160     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17161     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17162     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17163     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17164     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17165         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17166         FREE(cps->option[cps->nrOptions].name);
17167         cps->option[cps->nrOptions].name = q; q = NULL;
17168         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17169           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17170             SendToProgram(buf, cps);
17171             continue;
17172         }
17173         if(cps->nrOptions >= MAX_OPTIONS) {
17174             cps->nrOptions--;
17175             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17176             DisplayError(buf, 0);
17177         }
17178         continue;
17179     }
17180     /* End of additions by HGM */
17181
17182     /* unknown feature: complain and skip */
17183     q = p;
17184     while (*q && *q != '=') q++;
17185     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17186     SendToProgram(buf, cps);
17187     p = q;
17188     if (*p == '=') {
17189       p++;
17190       if (*p == '\"') {
17191         p++;
17192         while (*p && *p != '\"') p++;
17193         if (*p == '\"') p++;
17194       } else {
17195         while (*p && *p != ' ') p++;
17196       }
17197     }
17198   }
17199
17200 }
17201
17202 void
17203 PeriodicUpdatesEvent (int newState)
17204 {
17205     if (newState == appData.periodicUpdates)
17206       return;
17207
17208     appData.periodicUpdates=newState;
17209
17210     /* Display type changes, so update it now */
17211 //    DisplayAnalysis();
17212
17213     /* Get the ball rolling again... */
17214     if (newState) {
17215         AnalysisPeriodicEvent(1);
17216         StartAnalysisClock();
17217     }
17218 }
17219
17220 void
17221 PonderNextMoveEvent (int newState)
17222 {
17223     if (newState == appData.ponderNextMove) return;
17224     if (gameMode == EditPosition) EditPositionDone(TRUE);
17225     if (newState) {
17226         SendToProgram("hard\n", &first);
17227         if (gameMode == TwoMachinesPlay) {
17228             SendToProgram("hard\n", &second);
17229         }
17230     } else {
17231         SendToProgram("easy\n", &first);
17232         thinkOutput[0] = NULLCHAR;
17233         if (gameMode == TwoMachinesPlay) {
17234             SendToProgram("easy\n", &second);
17235         }
17236     }
17237     appData.ponderNextMove = newState;
17238 }
17239
17240 void
17241 NewSettingEvent (int option, int *feature, char *command, int value)
17242 {
17243     char buf[MSG_SIZ];
17244
17245     if (gameMode == EditPosition) EditPositionDone(TRUE);
17246     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17247     if(feature == NULL || *feature) SendToProgram(buf, &first);
17248     if (gameMode == TwoMachinesPlay) {
17249         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17250     }
17251 }
17252
17253 void
17254 ShowThinkingEvent ()
17255 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17256 {
17257     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17258     int newState = appData.showThinking
17259         // [HGM] thinking: other features now need thinking output as well
17260         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17261
17262     if (oldState == newState) return;
17263     oldState = newState;
17264     if (gameMode == EditPosition) EditPositionDone(TRUE);
17265     if (oldState) {
17266         SendToProgram("post\n", &first);
17267         if (gameMode == TwoMachinesPlay) {
17268             SendToProgram("post\n", &second);
17269         }
17270     } else {
17271         SendToProgram("nopost\n", &first);
17272         thinkOutput[0] = NULLCHAR;
17273         if (gameMode == TwoMachinesPlay) {
17274             SendToProgram("nopost\n", &second);
17275         }
17276     }
17277 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17278 }
17279
17280 void
17281 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17282 {
17283   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17284   if (pr == NoProc) return;
17285   AskQuestion(title, question, replyPrefix, pr);
17286 }
17287
17288 void
17289 TypeInEvent (char firstChar)
17290 {
17291     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17292         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17293         gameMode == AnalyzeMode || gameMode == EditGame ||
17294         gameMode == EditPosition || gameMode == IcsExamining ||
17295         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17296         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17297                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17298                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17299         gameMode == Training) PopUpMoveDialog(firstChar);
17300 }
17301
17302 void
17303 TypeInDoneEvent (char *move)
17304 {
17305         Board board;
17306         int n, fromX, fromY, toX, toY;
17307         char promoChar;
17308         ChessMove moveType;
17309
17310         // [HGM] FENedit
17311         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17312                 EditPositionPasteFEN(move);
17313                 return;
17314         }
17315         // [HGM] movenum: allow move number to be typed in any mode
17316         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17317           ToNrEvent(2*n-1);
17318           return;
17319         }
17320         // undocumented kludge: allow command-line option to be typed in!
17321         // (potentially fatal, and does not implement the effect of the option.)
17322         // should only be used for options that are values on which future decisions will be made,
17323         // and definitely not on options that would be used during initialization.
17324         if(strstr(move, "!!! -") == move) {
17325             ParseArgsFromString(move+4);
17326             return;
17327         }
17328
17329       if (gameMode != EditGame && currentMove != forwardMostMove &&
17330         gameMode != Training) {
17331         DisplayMoveError(_("Displayed move is not current"));
17332       } else {
17333         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17334           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17335         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17336         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17337           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17338           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17339         } else {
17340           DisplayMoveError(_("Could not parse move"));
17341         }
17342       }
17343 }
17344
17345 void
17346 DisplayMove (int moveNumber)
17347 {
17348     char message[MSG_SIZ];
17349     char res[MSG_SIZ];
17350     char cpThinkOutput[MSG_SIZ];
17351
17352     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17353
17354     if (moveNumber == forwardMostMove - 1 ||
17355         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17356
17357         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17358
17359         if (strchr(cpThinkOutput, '\n')) {
17360             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17361         }
17362     } else {
17363         *cpThinkOutput = NULLCHAR;
17364     }
17365
17366     /* [AS] Hide thinking from human user */
17367     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17368         *cpThinkOutput = NULLCHAR;
17369         if( thinkOutput[0] != NULLCHAR ) {
17370             int i;
17371
17372             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17373                 cpThinkOutput[i] = '.';
17374             }
17375             cpThinkOutput[i] = NULLCHAR;
17376             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17377         }
17378     }
17379
17380     if (moveNumber == forwardMostMove - 1 &&
17381         gameInfo.resultDetails != NULL) {
17382         if (gameInfo.resultDetails[0] == NULLCHAR) {
17383           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17384         } else {
17385           snprintf(res, MSG_SIZ, " {%s} %s",
17386                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17387         }
17388     } else {
17389         res[0] = NULLCHAR;
17390     }
17391
17392     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17393         DisplayMessage(res, cpThinkOutput);
17394     } else {
17395       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17396                 WhiteOnMove(moveNumber) ? " " : ".. ",
17397                 parseList[moveNumber], res);
17398         DisplayMessage(message, cpThinkOutput);
17399     }
17400 }
17401
17402 void
17403 DisplayComment (int moveNumber, char *text)
17404 {
17405     char title[MSG_SIZ];
17406
17407     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17408       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17409     } else {
17410       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17411               WhiteOnMove(moveNumber) ? " " : ".. ",
17412               parseList[moveNumber]);
17413     }
17414     if (text != NULL && (appData.autoDisplayComment || commentUp))
17415         CommentPopUp(title, text);
17416 }
17417
17418 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17419  * might be busy thinking or pondering.  It can be omitted if your
17420  * gnuchess is configured to stop thinking immediately on any user
17421  * input.  However, that gnuchess feature depends on the FIONREAD
17422  * ioctl, which does not work properly on some flavors of Unix.
17423  */
17424 void
17425 Attention (ChessProgramState *cps)
17426 {
17427 #if ATTENTION
17428     if (!cps->useSigint) return;
17429     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17430     switch (gameMode) {
17431       case MachinePlaysWhite:
17432       case MachinePlaysBlack:
17433       case TwoMachinesPlay:
17434       case IcsPlayingWhite:
17435       case IcsPlayingBlack:
17436       case AnalyzeMode:
17437       case AnalyzeFile:
17438         /* Skip if we know it isn't thinking */
17439         if (!cps->maybeThinking) return;
17440         if (appData.debugMode)
17441           fprintf(debugFP, "Interrupting %s\n", cps->which);
17442         InterruptChildProcess(cps->pr);
17443         cps->maybeThinking = FALSE;
17444         break;
17445       default:
17446         break;
17447     }
17448 #endif /*ATTENTION*/
17449 }
17450
17451 int
17452 CheckFlags ()
17453 {
17454     if (whiteTimeRemaining <= 0) {
17455         if (!whiteFlag) {
17456             whiteFlag = TRUE;
17457             if (appData.icsActive) {
17458                 if (appData.autoCallFlag &&
17459                     gameMode == IcsPlayingBlack && !blackFlag) {
17460                   SendToICS(ics_prefix);
17461                   SendToICS("flag\n");
17462                 }
17463             } else {
17464                 if (blackFlag) {
17465                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17466                 } else {
17467                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17468                     if (appData.autoCallFlag) {
17469                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17470                         return TRUE;
17471                     }
17472                 }
17473             }
17474         }
17475     }
17476     if (blackTimeRemaining <= 0) {
17477         if (!blackFlag) {
17478             blackFlag = TRUE;
17479             if (appData.icsActive) {
17480                 if (appData.autoCallFlag &&
17481                     gameMode == IcsPlayingWhite && !whiteFlag) {
17482                   SendToICS(ics_prefix);
17483                   SendToICS("flag\n");
17484                 }
17485             } else {
17486                 if (whiteFlag) {
17487                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17488                 } else {
17489                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17490                     if (appData.autoCallFlag) {
17491                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17492                         return TRUE;
17493                     }
17494                 }
17495             }
17496         }
17497     }
17498     return FALSE;
17499 }
17500
17501 void
17502 CheckTimeControl ()
17503 {
17504     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17505         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17506
17507     /*
17508      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17509      */
17510     if ( !WhiteOnMove(forwardMostMove) ) {
17511         /* White made time control */
17512         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17513         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17514         /* [HGM] time odds: correct new time quota for time odds! */
17515                                             / WhitePlayer()->timeOdds;
17516         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17517     } else {
17518         lastBlack -= blackTimeRemaining;
17519         /* Black made time control */
17520         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17521                                             / WhitePlayer()->other->timeOdds;
17522         lastWhite = whiteTimeRemaining;
17523     }
17524 }
17525
17526 void
17527 DisplayBothClocks ()
17528 {
17529     int wom = gameMode == EditPosition ?
17530       !blackPlaysFirst : WhiteOnMove(currentMove);
17531     DisplayWhiteClock(whiteTimeRemaining, wom);
17532     DisplayBlackClock(blackTimeRemaining, !wom);
17533 }
17534
17535
17536 /* Timekeeping seems to be a portability nightmare.  I think everyone
17537    has ftime(), but I'm really not sure, so I'm including some ifdefs
17538    to use other calls if you don't.  Clocks will be less accurate if
17539    you have neither ftime nor gettimeofday.
17540 */
17541
17542 /* VS 2008 requires the #include outside of the function */
17543 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17544 #include <sys/timeb.h>
17545 #endif
17546
17547 /* Get the current time as a TimeMark */
17548 void
17549 GetTimeMark (TimeMark *tm)
17550 {
17551 #if HAVE_GETTIMEOFDAY
17552
17553     struct timeval timeVal;
17554     struct timezone timeZone;
17555
17556     gettimeofday(&timeVal, &timeZone);
17557     tm->sec = (long) timeVal.tv_sec;
17558     tm->ms = (int) (timeVal.tv_usec / 1000L);
17559
17560 #else /*!HAVE_GETTIMEOFDAY*/
17561 #if HAVE_FTIME
17562
17563 // include <sys/timeb.h> / moved to just above start of function
17564     struct timeb timeB;
17565
17566     ftime(&timeB);
17567     tm->sec = (long) timeB.time;
17568     tm->ms = (int) timeB.millitm;
17569
17570 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17571     tm->sec = (long) time(NULL);
17572     tm->ms = 0;
17573 #endif
17574 #endif
17575 }
17576
17577 /* Return the difference in milliseconds between two
17578    time marks.  We assume the difference will fit in a long!
17579 */
17580 long
17581 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17582 {
17583     return 1000L*(tm2->sec - tm1->sec) +
17584            (long) (tm2->ms - tm1->ms);
17585 }
17586
17587
17588 /*
17589  * Code to manage the game clocks.
17590  *
17591  * In tournament play, black starts the clock and then white makes a move.
17592  * We give the human user a slight advantage if he is playing white---the
17593  * clocks don't run until he makes his first move, so it takes zero time.
17594  * Also, we don't account for network lag, so we could get out of sync
17595  * with GNU Chess's clock -- but then, referees are always right.
17596  */
17597
17598 static TimeMark tickStartTM;
17599 static long intendedTickLength;
17600
17601 long
17602 NextTickLength (long timeRemaining)
17603 {
17604     long nominalTickLength, nextTickLength;
17605
17606     if (timeRemaining > 0L && timeRemaining <= 10000L)
17607       nominalTickLength = 100L;
17608     else
17609       nominalTickLength = 1000L;
17610     nextTickLength = timeRemaining % nominalTickLength;
17611     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17612
17613     return nextTickLength;
17614 }
17615
17616 /* Adjust clock one minute up or down */
17617 void
17618 AdjustClock (Boolean which, int dir)
17619 {
17620     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17621     if(which) blackTimeRemaining += 60000*dir;
17622     else      whiteTimeRemaining += 60000*dir;
17623     DisplayBothClocks();
17624     adjustedClock = TRUE;
17625 }
17626
17627 /* Stop clocks and reset to a fresh time control */
17628 void
17629 ResetClocks ()
17630 {
17631     (void) StopClockTimer();
17632     if (appData.icsActive) {
17633         whiteTimeRemaining = blackTimeRemaining = 0;
17634     } else if (searchTime) {
17635         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17636         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17637     } else { /* [HGM] correct new time quote for time odds */
17638         whiteTC = blackTC = fullTimeControlString;
17639         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17640         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17641     }
17642     if (whiteFlag || blackFlag) {
17643         DisplayTitle("");
17644         whiteFlag = blackFlag = FALSE;
17645     }
17646     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17647     DisplayBothClocks();
17648     adjustedClock = FALSE;
17649 }
17650
17651 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17652
17653 /* Decrement running clock by amount of time that has passed */
17654 void
17655 DecrementClocks ()
17656 {
17657     long timeRemaining;
17658     long lastTickLength, fudge;
17659     TimeMark now;
17660
17661     if (!appData.clockMode) return;
17662     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17663
17664     GetTimeMark(&now);
17665
17666     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17667
17668     /* Fudge if we woke up a little too soon */
17669     fudge = intendedTickLength - lastTickLength;
17670     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17671
17672     if (WhiteOnMove(forwardMostMove)) {
17673         if(whiteNPS >= 0) lastTickLength = 0;
17674         timeRemaining = whiteTimeRemaining -= lastTickLength;
17675         if(timeRemaining < 0 && !appData.icsActive) {
17676             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17677             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17678                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17679                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17680             }
17681         }
17682         DisplayWhiteClock(whiteTimeRemaining - fudge,
17683                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17684     } else {
17685         if(blackNPS >= 0) lastTickLength = 0;
17686         timeRemaining = blackTimeRemaining -= lastTickLength;
17687         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17688             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17689             if(suddenDeath) {
17690                 blackStartMove = forwardMostMove;
17691                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17692             }
17693         }
17694         DisplayBlackClock(blackTimeRemaining - fudge,
17695                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17696     }
17697     if (CheckFlags()) return;
17698
17699     if(twoBoards) { // count down secondary board's clocks as well
17700         activePartnerTime -= lastTickLength;
17701         partnerUp = 1;
17702         if(activePartner == 'W')
17703             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17704         else
17705             DisplayBlackClock(activePartnerTime, TRUE);
17706         partnerUp = 0;
17707     }
17708
17709     tickStartTM = now;
17710     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17711     StartClockTimer(intendedTickLength);
17712
17713     /* if the time remaining has fallen below the alarm threshold, sound the
17714      * alarm. if the alarm has sounded and (due to a takeback or time control
17715      * with increment) the time remaining has increased to a level above the
17716      * threshold, reset the alarm so it can sound again.
17717      */
17718
17719     if (appData.icsActive && appData.icsAlarm) {
17720
17721         /* make sure we are dealing with the user's clock */
17722         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17723                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17724            )) return;
17725
17726         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17727             alarmSounded = FALSE;
17728         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17729             PlayAlarmSound();
17730             alarmSounded = TRUE;
17731         }
17732     }
17733 }
17734
17735
17736 /* A player has just moved, so stop the previously running
17737    clock and (if in clock mode) start the other one.
17738    We redisplay both clocks in case we're in ICS mode, because
17739    ICS gives us an update to both clocks after every move.
17740    Note that this routine is called *after* forwardMostMove
17741    is updated, so the last fractional tick must be subtracted
17742    from the color that is *not* on move now.
17743 */
17744 void
17745 SwitchClocks (int newMoveNr)
17746 {
17747     long lastTickLength;
17748     TimeMark now;
17749     int flagged = FALSE;
17750
17751     GetTimeMark(&now);
17752
17753     if (StopClockTimer() && appData.clockMode) {
17754         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17755         if (!WhiteOnMove(forwardMostMove)) {
17756             if(blackNPS >= 0) lastTickLength = 0;
17757             blackTimeRemaining -= lastTickLength;
17758            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17759 //         if(pvInfoList[forwardMostMove].time == -1)
17760                  pvInfoList[forwardMostMove].time =               // use GUI time
17761                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17762         } else {
17763            if(whiteNPS >= 0) lastTickLength = 0;
17764            whiteTimeRemaining -= lastTickLength;
17765            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17766 //         if(pvInfoList[forwardMostMove].time == -1)
17767                  pvInfoList[forwardMostMove].time =
17768                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17769         }
17770         flagged = CheckFlags();
17771     }
17772     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17773     CheckTimeControl();
17774
17775     if (flagged || !appData.clockMode) return;
17776
17777     switch (gameMode) {
17778       case MachinePlaysBlack:
17779       case MachinePlaysWhite:
17780       case BeginningOfGame:
17781         if (pausing) return;
17782         break;
17783
17784       case EditGame:
17785       case PlayFromGameFile:
17786       case IcsExamining:
17787         return;
17788
17789       default:
17790         break;
17791     }
17792
17793     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17794         if(WhiteOnMove(forwardMostMove))
17795              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17796         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17797     }
17798
17799     tickStartTM = now;
17800     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17801       whiteTimeRemaining : blackTimeRemaining);
17802     StartClockTimer(intendedTickLength);
17803 }
17804
17805
17806 /* Stop both clocks */
17807 void
17808 StopClocks ()
17809 {
17810     long lastTickLength;
17811     TimeMark now;
17812
17813     if (!StopClockTimer()) return;
17814     if (!appData.clockMode) return;
17815
17816     GetTimeMark(&now);
17817
17818     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17819     if (WhiteOnMove(forwardMostMove)) {
17820         if(whiteNPS >= 0) lastTickLength = 0;
17821         whiteTimeRemaining -= lastTickLength;
17822         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17823     } else {
17824         if(blackNPS >= 0) lastTickLength = 0;
17825         blackTimeRemaining -= lastTickLength;
17826         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17827     }
17828     CheckFlags();
17829 }
17830
17831 /* Start clock of player on move.  Time may have been reset, so
17832    if clock is already running, stop and restart it. */
17833 void
17834 StartClocks ()
17835 {
17836     (void) StopClockTimer(); /* in case it was running already */
17837     DisplayBothClocks();
17838     if (CheckFlags()) return;
17839
17840     if (!appData.clockMode) return;
17841     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17842
17843     GetTimeMark(&tickStartTM);
17844     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17845       whiteTimeRemaining : blackTimeRemaining);
17846
17847    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17848     whiteNPS = blackNPS = -1;
17849     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17850        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17851         whiteNPS = first.nps;
17852     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17853        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17854         blackNPS = first.nps;
17855     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17856         whiteNPS = second.nps;
17857     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17858         blackNPS = second.nps;
17859     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17860
17861     StartClockTimer(intendedTickLength);
17862 }
17863
17864 char *
17865 TimeString (long ms)
17866 {
17867     long second, minute, hour, day;
17868     char *sign = "";
17869     static char buf[32];
17870
17871     if (ms > 0 && ms <= 9900) {
17872       /* convert milliseconds to tenths, rounding up */
17873       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17874
17875       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17876       return buf;
17877     }
17878
17879     /* convert milliseconds to seconds, rounding up */
17880     /* use floating point to avoid strangeness of integer division
17881        with negative dividends on many machines */
17882     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17883
17884     if (second < 0) {
17885         sign = "-";
17886         second = -second;
17887     }
17888
17889     day = second / (60 * 60 * 24);
17890     second = second % (60 * 60 * 24);
17891     hour = second / (60 * 60);
17892     second = second % (60 * 60);
17893     minute = second / 60;
17894     second = second % 60;
17895
17896     if (day > 0)
17897       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17898               sign, day, hour, minute, second);
17899     else if (hour > 0)
17900       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17901     else
17902       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17903
17904     return buf;
17905 }
17906
17907
17908 /*
17909  * This is necessary because some C libraries aren't ANSI C compliant yet.
17910  */
17911 char *
17912 StrStr (char *string, char *match)
17913 {
17914     int i, length;
17915
17916     length = strlen(match);
17917
17918     for (i = strlen(string) - length; i >= 0; i--, string++)
17919       if (!strncmp(match, string, length))
17920         return string;
17921
17922     return NULL;
17923 }
17924
17925 char *
17926 StrCaseStr (char *string, char *match)
17927 {
17928     int i, j, length;
17929
17930     length = strlen(match);
17931
17932     for (i = strlen(string) - length; i >= 0; i--, string++) {
17933         for (j = 0; j < length; j++) {
17934             if (ToLower(match[j]) != ToLower(string[j]))
17935               break;
17936         }
17937         if (j == length) return string;
17938     }
17939
17940     return NULL;
17941 }
17942
17943 #ifndef _amigados
17944 int
17945 StrCaseCmp (char *s1, char *s2)
17946 {
17947     char c1, c2;
17948
17949     for (;;) {
17950         c1 = ToLower(*s1++);
17951         c2 = ToLower(*s2++);
17952         if (c1 > c2) return 1;
17953         if (c1 < c2) return -1;
17954         if (c1 == NULLCHAR) return 0;
17955     }
17956 }
17957
17958
17959 int
17960 ToLower (int c)
17961 {
17962     return isupper(c) ? tolower(c) : c;
17963 }
17964
17965
17966 int
17967 ToUpper (int c)
17968 {
17969     return islower(c) ? toupper(c) : c;
17970 }
17971 #endif /* !_amigados    */
17972
17973 char *
17974 StrSave (char *s)
17975 {
17976   char *ret;
17977
17978   if ((ret = (char *) malloc(strlen(s) + 1)))
17979     {
17980       safeStrCpy(ret, s, strlen(s)+1);
17981     }
17982   return ret;
17983 }
17984
17985 char *
17986 StrSavePtr (char *s, char **savePtr)
17987 {
17988     if (*savePtr) {
17989         free(*savePtr);
17990     }
17991     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17992       safeStrCpy(*savePtr, s, strlen(s)+1);
17993     }
17994     return(*savePtr);
17995 }
17996
17997 char *
17998 PGNDate ()
17999 {
18000     time_t clock;
18001     struct tm *tm;
18002     char buf[MSG_SIZ];
18003
18004     clock = time((time_t *)NULL);
18005     tm = localtime(&clock);
18006     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18007             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18008     return StrSave(buf);
18009 }
18010
18011
18012 char *
18013 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18014 {
18015     int i, j, fromX, fromY, toX, toY;
18016     int whiteToPlay, haveRights = nrCastlingRights;
18017     char buf[MSG_SIZ];
18018     char *p, *q;
18019     int emptycount;
18020     ChessSquare piece;
18021
18022     whiteToPlay = (gameMode == EditPosition) ?
18023       !blackPlaysFirst : (move % 2 == 0);
18024     p = buf;
18025
18026     /* Piece placement data */
18027     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18028         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18029         emptycount = 0;
18030         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18031             if (boards[move][i][j] == EmptySquare) {
18032                 emptycount++;
18033             } else { ChessSquare piece = boards[move][i][j];
18034                 if (emptycount > 0) {
18035                     if(emptycount<10) /* [HGM] can be >= 10 */
18036                         *p++ = '0' + emptycount;
18037                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18038                     emptycount = 0;
18039                 }
18040                 if(PieceToChar(piece) == '+') {
18041                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18042                     *p++ = '+';
18043                     piece = (ChessSquare)(CHUDEMOTED(piece));
18044                 }
18045                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18046                 if(*p = PieceSuffix(piece)) p++;
18047                 if(p[-1] == '~') {
18048                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18049                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18050                     *p++ = '~';
18051                 }
18052             }
18053         }
18054         if (emptycount > 0) {
18055             if(emptycount<10) /* [HGM] can be >= 10 */
18056                 *p++ = '0' + emptycount;
18057             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18058             emptycount = 0;
18059         }
18060         *p++ = '/';
18061     }
18062     *(p - 1) = ' ';
18063
18064     /* [HGM] print Crazyhouse or Shogi holdings */
18065     if( gameInfo.holdingsWidth ) {
18066         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18067         q = p;
18068         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18069             piece = boards[move][i][BOARD_WIDTH-1];
18070             if( piece != EmptySquare )
18071               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18072                   *p++ = PieceToChar(piece);
18073         }
18074         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18075             piece = boards[move][BOARD_HEIGHT-i-1][0];
18076             if( piece != EmptySquare )
18077               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18078                   *p++ = PieceToChar(piece);
18079         }
18080
18081         if( q == p ) *p++ = '-';
18082         *p++ = ']';
18083         *p++ = ' ';
18084     }
18085
18086     /* Active color */
18087     *p++ = whiteToPlay ? 'w' : 'b';
18088     *p++ = ' ';
18089
18090   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18091     haveRights = 0; q = p;
18092     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18093       piece = boards[move][0][i];
18094       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18095         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18096       }
18097     }
18098     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18099       piece = boards[move][BOARD_HEIGHT-1][i];
18100       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18101         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18102       }
18103     }
18104     if(p == q) *p++ = '-';
18105     *p++ = ' ';
18106   }
18107
18108   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18109     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18110   } else {
18111   if(haveRights) {
18112      int handW=0, handB=0;
18113      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18114         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18115         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18116      }
18117      q = p;
18118      if(appData.fischerCastling) {
18119         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18120            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18121                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18122         } else {
18123        /* [HGM] write directly from rights */
18124            if(boards[move][CASTLING][2] != NoRights &&
18125               boards[move][CASTLING][0] != NoRights   )
18126                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18127            if(boards[move][CASTLING][2] != NoRights &&
18128               boards[move][CASTLING][1] != NoRights   )
18129                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18130         }
18131         if(handB) {
18132            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18133                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18134         } else {
18135            if(boards[move][CASTLING][5] != NoRights &&
18136               boards[move][CASTLING][3] != NoRights   )
18137                 *p++ = boards[move][CASTLING][3] + AAA;
18138            if(boards[move][CASTLING][5] != NoRights &&
18139               boards[move][CASTLING][4] != NoRights   )
18140                 *p++ = boards[move][CASTLING][4] + AAA;
18141         }
18142      } else {
18143
18144         /* [HGM] write true castling rights */
18145         if( nrCastlingRights == 6 ) {
18146             int q, k=0;
18147             if(boards[move][CASTLING][0] != NoRights &&
18148                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18149             q = (boards[move][CASTLING][1] != NoRights &&
18150                  boards[move][CASTLING][2] != NoRights  );
18151             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18152                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18153                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18154                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18155             }
18156             if(q) *p++ = 'Q';
18157             k = 0;
18158             if(boards[move][CASTLING][3] != NoRights &&
18159                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18160             q = (boards[move][CASTLING][4] != NoRights &&
18161                  boards[move][CASTLING][5] != NoRights  );
18162             if(handB) {
18163                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18164                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18165                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18166             }
18167             if(q) *p++ = 'q';
18168         }
18169      }
18170      if (q == p) *p++ = '-'; /* No castling rights */
18171      *p++ = ' ';
18172   }
18173
18174   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18175      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18176      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18177     /* En passant target square */
18178     if (move > backwardMostMove) {
18179         fromX = moveList[move - 1][0] - AAA;
18180         fromY = moveList[move - 1][1] - ONE;
18181         toX = moveList[move - 1][2] - AAA;
18182         toY = moveList[move - 1][3] - ONE;
18183         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18184             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18185             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18186             fromX == toX) {
18187             /* 2-square pawn move just happened */
18188             *p++ = toX + AAA;
18189             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18190         } else {
18191             *p++ = '-';
18192         }
18193     } else if(move == backwardMostMove) {
18194         // [HGM] perhaps we should always do it like this, and forget the above?
18195         if((signed char)boards[move][EP_STATUS] >= 0) {
18196             *p++ = boards[move][EP_STATUS] + AAA;
18197             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18198         } else {
18199             *p++ = '-';
18200         }
18201     } else {
18202         *p++ = '-';
18203     }
18204     *p++ = ' ';
18205   }
18206   }
18207
18208     if(moveCounts)
18209     {   int i = 0, j=move;
18210
18211         /* [HGM] find reversible plies */
18212         if (appData.debugMode) { int k;
18213             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18214             for(k=backwardMostMove; k<=forwardMostMove; k++)
18215                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18216
18217         }
18218
18219         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18220         if( j == backwardMostMove ) i += initialRulePlies;
18221         sprintf(p, "%d ", i);
18222         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18223
18224         /* Fullmove number */
18225         sprintf(p, "%d", (move / 2) + 1);
18226     } else *--p = NULLCHAR;
18227
18228     return StrSave(buf);
18229 }
18230
18231 Boolean
18232 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18233 {
18234     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18235     char *p, c;
18236     int emptycount, virgin[BOARD_FILES];
18237     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18238
18239     p = fen;
18240
18241     /* Piece placement data */
18242     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18243         j = 0;
18244         for (;;) {
18245             if (*p == '/' || *p == ' ' || *p == '[' ) {
18246                 if(j > w) w = j;
18247                 emptycount = gameInfo.boardWidth - j;
18248                 while (emptycount--)
18249                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18250                 if (*p == '/') p++;
18251                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18252                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18253                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18254                     }
18255                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18256                 }
18257                 break;
18258 #if(BOARD_FILES >= 10)*0
18259             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18260                 p++; emptycount=10;
18261                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18262                 while (emptycount--)
18263                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18264 #endif
18265             } else if (*p == '*') {
18266                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18267             } else if (isdigit(*p)) {
18268                 emptycount = *p++ - '0';
18269                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18270                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18271                 while (emptycount--)
18272                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18273             } else if (*p == '<') {
18274                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18275                 else if (i != 0 || !shuffle) return FALSE;
18276                 p++;
18277             } else if (shuffle && *p == '>') {
18278                 p++; // for now ignore closing shuffle range, and assume rank-end
18279             } else if (*p == '?') {
18280                 if (j >= gameInfo.boardWidth) return FALSE;
18281                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18282                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18283             } else if (*p == '+' || isalpha(*p)) {
18284                 char *q, *s = SUFFIXES;
18285                 if (j >= gameInfo.boardWidth) return FALSE;
18286                 if(*p=='+') {
18287                     char c = *++p;
18288                     if(q = strchr(s, p[1])) p++;
18289                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18290                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18291                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18292                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18293                 } else {
18294                     char c = *p++;
18295                     if(q = strchr(s, *p)) p++;
18296                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18297                 }
18298
18299                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18300                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18301                     piece = (ChessSquare) (PROMOTED(piece));
18302                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18303                     p++;
18304                 }
18305                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18306                 if(piece == king) wKingRank = i;
18307                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18308             } else {
18309                 return FALSE;
18310             }
18311         }
18312     }
18313     while (*p == '/' || *p == ' ') p++;
18314
18315     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18316
18317     /* [HGM] by default clear Crazyhouse holdings, if present */
18318     if(gameInfo.holdingsWidth) {
18319        for(i=0; i<BOARD_HEIGHT; i++) {
18320            board[i][0]             = EmptySquare; /* black holdings */
18321            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18322            board[i][1]             = (ChessSquare) 0; /* black counts */
18323            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18324        }
18325     }
18326
18327     /* [HGM] look for Crazyhouse holdings here */
18328     while(*p==' ') p++;
18329     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18330         int swap=0, wcnt=0, bcnt=0;
18331         if(*p == '[') p++;
18332         if(*p == '<') swap++, p++;
18333         if(*p == '-' ) p++; /* empty holdings */ else {
18334             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18335             /* if we would allow FEN reading to set board size, we would   */
18336             /* have to add holdings and shift the board read so far here   */
18337             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18338                 p++;
18339                 if((int) piece >= (int) BlackPawn ) {
18340                     i = (int)piece - (int)BlackPawn;
18341                     i = PieceToNumber((ChessSquare)i);
18342                     if( i >= gameInfo.holdingsSize ) return FALSE;
18343                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18344                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18345                     bcnt++;
18346                 } else {
18347                     i = (int)piece - (int)WhitePawn;
18348                     i = PieceToNumber((ChessSquare)i);
18349                     if( i >= gameInfo.holdingsSize ) return FALSE;
18350                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18351                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18352                     wcnt++;
18353                 }
18354             }
18355             if(subst) { // substitute back-rank question marks by holdings pieces
18356                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18357                     int k, m, n = bcnt + 1;
18358                     if(board[0][j] == ClearBoard) {
18359                         if(!wcnt) return FALSE;
18360                         n = rand() % wcnt;
18361                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18362                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18363                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18364                             break;
18365                         }
18366                     }
18367                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18368                         if(!bcnt) return FALSE;
18369                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18370                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18371                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18372                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18373                             break;
18374                         }
18375                     }
18376                 }
18377                 subst = 0;
18378             }
18379         }
18380         if(*p == ']') p++;
18381     }
18382
18383     if(subst) return FALSE; // substitution requested, but no holdings
18384
18385     while(*p == ' ') p++;
18386
18387     /* Active color */
18388     c = *p++;
18389     if(appData.colorNickNames) {
18390       if( c == appData.colorNickNames[0] ) c = 'w'; else
18391       if( c == appData.colorNickNames[1] ) c = 'b';
18392     }
18393     switch (c) {
18394       case 'w':
18395         *blackPlaysFirst = FALSE;
18396         break;
18397       case 'b':
18398         *blackPlaysFirst = TRUE;
18399         break;
18400       default:
18401         return FALSE;
18402     }
18403
18404     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18405     /* return the extra info in global variiables             */
18406
18407     while(*p==' ') p++;
18408
18409     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18410         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18411         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18412     }
18413
18414     /* set defaults in case FEN is incomplete */
18415     board[EP_STATUS] = EP_UNKNOWN;
18416     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18417     for(i=0; i<nrCastlingRights; i++ ) {
18418         board[CASTLING][i] =
18419             appData.fischerCastling ? NoRights : initialRights[i];
18420     }   /* assume possible unless obviously impossible */
18421     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18422     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18423     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18424                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18425     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18426     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18427     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18428                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18429     FENrulePlies = 0;
18430
18431     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18432       char *q = p;
18433       int w=0, b=0;
18434       while(isalpha(*p)) {
18435         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18436         if(islower(*p)) b |= 1 << (*p++ - 'a');
18437       }
18438       if(*p == '-') p++;
18439       if(p != q) {
18440         board[TOUCHED_W] = ~w;
18441         board[TOUCHED_B] = ~b;
18442         while(*p == ' ') p++;
18443       }
18444     } else
18445
18446     if(nrCastlingRights) {
18447       int fischer = 0;
18448       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18449       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18450           /* castling indicator present, so default becomes no castlings */
18451           for(i=0; i<nrCastlingRights; i++ ) {
18452                  board[CASTLING][i] = NoRights;
18453           }
18454       }
18455       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18456              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18457              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18458              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18459         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18460
18461         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18462             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18463             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18464         }
18465         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18466             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18467         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18468                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18469         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18470                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18471         switch(c) {
18472           case'K':
18473               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18474               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18475               board[CASTLING][2] = whiteKingFile;
18476               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18477               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18478               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18479               break;
18480           case'Q':
18481               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18482               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18483               board[CASTLING][2] = whiteKingFile;
18484               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18485               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18486               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18487               break;
18488           case'k':
18489               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18490               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18491               board[CASTLING][5] = blackKingFile;
18492               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18493               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18494               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18495               break;
18496           case'q':
18497               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18498               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18499               board[CASTLING][5] = blackKingFile;
18500               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18501               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18502               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18503           case '-':
18504               break;
18505           default: /* FRC castlings */
18506               if(c >= 'a') { /* black rights */
18507                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18508                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18509                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18510                   if(i == BOARD_RGHT) break;
18511                   board[CASTLING][5] = i;
18512                   c -= AAA;
18513                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18514                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18515                   if(c > i)
18516                       board[CASTLING][3] = c;
18517                   else
18518                       board[CASTLING][4] = c;
18519               } else { /* white rights */
18520                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18521                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18522                     if(board[0][i] == WhiteKing) break;
18523                   if(i == BOARD_RGHT) break;
18524                   board[CASTLING][2] = i;
18525                   c -= AAA - 'a' + 'A';
18526                   if(board[0][c] >= WhiteKing) break;
18527                   if(c > i)
18528                       board[CASTLING][0] = c;
18529                   else
18530                       board[CASTLING][1] = c;
18531               }
18532         }
18533       }
18534       for(i=0; i<nrCastlingRights; i++)
18535         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18536       if(gameInfo.variant == VariantSChess)
18537         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18538       if(fischer && shuffle) appData.fischerCastling = TRUE;
18539     if (appData.debugMode) {
18540         fprintf(debugFP, "FEN castling rights:");
18541         for(i=0; i<nrCastlingRights; i++)
18542         fprintf(debugFP, " %d", board[CASTLING][i]);
18543         fprintf(debugFP, "\n");
18544     }
18545
18546       while(*p==' ') p++;
18547     }
18548
18549     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18550
18551     /* read e.p. field in games that know e.p. capture */
18552     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18553        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18554        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18555       if(*p=='-') {
18556         p++; board[EP_STATUS] = EP_NONE;
18557       } else {
18558          char c = *p++ - AAA;
18559
18560          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18561          if(*p >= '0' && *p <='9') p++;
18562          board[EP_STATUS] = c;
18563       }
18564     }
18565
18566
18567     if(sscanf(p, "%d", &i) == 1) {
18568         FENrulePlies = i; /* 50-move ply counter */
18569         /* (The move number is still ignored)    */
18570     }
18571
18572     return TRUE;
18573 }
18574
18575 void
18576 EditPositionPasteFEN (char *fen)
18577 {
18578   if (fen != NULL) {
18579     Board initial_position;
18580
18581     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18582       DisplayError(_("Bad FEN position in clipboard"), 0);
18583       return ;
18584     } else {
18585       int savedBlackPlaysFirst = blackPlaysFirst;
18586       EditPositionEvent();
18587       blackPlaysFirst = savedBlackPlaysFirst;
18588       CopyBoard(boards[0], initial_position);
18589       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18590       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18591       DisplayBothClocks();
18592       DrawPosition(FALSE, boards[currentMove]);
18593     }
18594   }
18595 }
18596
18597 static char cseq[12] = "\\   ";
18598
18599 Boolean
18600 set_cont_sequence (char *new_seq)
18601 {
18602     int len;
18603     Boolean ret;
18604
18605     // handle bad attempts to set the sequence
18606         if (!new_seq)
18607                 return 0; // acceptable error - no debug
18608
18609     len = strlen(new_seq);
18610     ret = (len > 0) && (len < sizeof(cseq));
18611     if (ret)
18612       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18613     else if (appData.debugMode)
18614       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18615     return ret;
18616 }
18617
18618 /*
18619     reformat a source message so words don't cross the width boundary.  internal
18620     newlines are not removed.  returns the wrapped size (no null character unless
18621     included in source message).  If dest is NULL, only calculate the size required
18622     for the dest buffer.  lp argument indicats line position upon entry, and it's
18623     passed back upon exit.
18624 */
18625 int
18626 wrap (char *dest, char *src, int count, int width, int *lp)
18627 {
18628     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18629
18630     cseq_len = strlen(cseq);
18631     old_line = line = *lp;
18632     ansi = len = clen = 0;
18633
18634     for (i=0; i < count; i++)
18635     {
18636         if (src[i] == '\033')
18637             ansi = 1;
18638
18639         // if we hit the width, back up
18640         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18641         {
18642             // store i & len in case the word is too long
18643             old_i = i, old_len = len;
18644
18645             // find the end of the last word
18646             while (i && src[i] != ' ' && src[i] != '\n')
18647             {
18648                 i--;
18649                 len--;
18650             }
18651
18652             // word too long?  restore i & len before splitting it
18653             if ((old_i-i+clen) >= width)
18654             {
18655                 i = old_i;
18656                 len = old_len;
18657             }
18658
18659             // extra space?
18660             if (i && src[i-1] == ' ')
18661                 len--;
18662
18663             if (src[i] != ' ' && src[i] != '\n')
18664             {
18665                 i--;
18666                 if (len)
18667                     len--;
18668             }
18669
18670             // now append the newline and continuation sequence
18671             if (dest)
18672                 dest[len] = '\n';
18673             len++;
18674             if (dest)
18675                 strncpy(dest+len, cseq, cseq_len);
18676             len += cseq_len;
18677             line = cseq_len;
18678             clen = cseq_len;
18679             continue;
18680         }
18681
18682         if (dest)
18683             dest[len] = src[i];
18684         len++;
18685         if (!ansi)
18686             line++;
18687         if (src[i] == '\n')
18688             line = 0;
18689         if (src[i] == 'm')
18690             ansi = 0;
18691     }
18692     if (dest && appData.debugMode)
18693     {
18694         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18695             count, width, line, len, *lp);
18696         show_bytes(debugFP, src, count);
18697         fprintf(debugFP, "\ndest: ");
18698         show_bytes(debugFP, dest, len);
18699         fprintf(debugFP, "\n");
18700     }
18701     *lp = dest ? line : old_line;
18702
18703     return len;
18704 }
18705
18706 // [HGM] vari: routines for shelving variations
18707 Boolean modeRestore = FALSE;
18708
18709 void
18710 PushInner (int firstMove, int lastMove)
18711 {
18712         int i, j, nrMoves = lastMove - firstMove;
18713
18714         // push current tail of game on stack
18715         savedResult[storedGames] = gameInfo.result;
18716         savedDetails[storedGames] = gameInfo.resultDetails;
18717         gameInfo.resultDetails = NULL;
18718         savedFirst[storedGames] = firstMove;
18719         savedLast [storedGames] = lastMove;
18720         savedFramePtr[storedGames] = framePtr;
18721         framePtr -= nrMoves; // reserve space for the boards
18722         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18723             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18724             for(j=0; j<MOVE_LEN; j++)
18725                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18726             for(j=0; j<2*MOVE_LEN; j++)
18727                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18728             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18729             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18730             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18731             pvInfoList[firstMove+i-1].depth = 0;
18732             commentList[framePtr+i] = commentList[firstMove+i];
18733             commentList[firstMove+i] = NULL;
18734         }
18735
18736         storedGames++;
18737         forwardMostMove = firstMove; // truncate game so we can start variation
18738 }
18739
18740 void
18741 PushTail (int firstMove, int lastMove)
18742 {
18743         if(appData.icsActive) { // only in local mode
18744                 forwardMostMove = currentMove; // mimic old ICS behavior
18745                 return;
18746         }
18747         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18748
18749         PushInner(firstMove, lastMove);
18750         if(storedGames == 1) GreyRevert(FALSE);
18751         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18752 }
18753
18754 void
18755 PopInner (Boolean annotate)
18756 {
18757         int i, j, nrMoves;
18758         char buf[8000], moveBuf[20];
18759
18760         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18761         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18762         nrMoves = savedLast[storedGames] - currentMove;
18763         if(annotate) {
18764                 int cnt = 10;
18765                 if(!WhiteOnMove(currentMove))
18766                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18767                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18768                 for(i=currentMove; i<forwardMostMove; i++) {
18769                         if(WhiteOnMove(i))
18770                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18771                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18772                         strcat(buf, moveBuf);
18773                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18774                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18775                 }
18776                 strcat(buf, ")");
18777         }
18778         for(i=1; i<=nrMoves; i++) { // copy last variation back
18779             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18780             for(j=0; j<MOVE_LEN; j++)
18781                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18782             for(j=0; j<2*MOVE_LEN; j++)
18783                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18784             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18785             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18786             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18787             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18788             commentList[currentMove+i] = commentList[framePtr+i];
18789             commentList[framePtr+i] = NULL;
18790         }
18791         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18792         framePtr = savedFramePtr[storedGames];
18793         gameInfo.result = savedResult[storedGames];
18794         if(gameInfo.resultDetails != NULL) {
18795             free(gameInfo.resultDetails);
18796       }
18797         gameInfo.resultDetails = savedDetails[storedGames];
18798         forwardMostMove = currentMove + nrMoves;
18799 }
18800
18801 Boolean
18802 PopTail (Boolean annotate)
18803 {
18804         if(appData.icsActive) return FALSE; // only in local mode
18805         if(!storedGames) return FALSE; // sanity
18806         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18807
18808         PopInner(annotate);
18809         if(currentMove < forwardMostMove) ForwardEvent(); else
18810         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18811
18812         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18813         return TRUE;
18814 }
18815
18816 void
18817 CleanupTail ()
18818 {       // remove all shelved variations
18819         int i;
18820         for(i=0; i<storedGames; i++) {
18821             if(savedDetails[i])
18822                 free(savedDetails[i]);
18823             savedDetails[i] = NULL;
18824         }
18825         for(i=framePtr; i<MAX_MOVES; i++) {
18826                 if(commentList[i]) free(commentList[i]);
18827                 commentList[i] = NULL;
18828         }
18829         framePtr = MAX_MOVES-1;
18830         storedGames = 0;
18831 }
18832
18833 void
18834 LoadVariation (int index, char *text)
18835 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18836         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18837         int level = 0, move;
18838
18839         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18840         // first find outermost bracketing variation
18841         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18842             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18843                 if(*p == '{') wait = '}'; else
18844                 if(*p == '[') wait = ']'; else
18845                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18846                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18847             }
18848             if(*p == wait) wait = NULLCHAR; // closing ]} found
18849             p++;
18850         }
18851         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18852         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18853         end[1] = NULLCHAR; // clip off comment beyond variation
18854         ToNrEvent(currentMove-1);
18855         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18856         // kludge: use ParsePV() to append variation to game
18857         move = currentMove;
18858         ParsePV(start, TRUE, TRUE);
18859         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18860         ClearPremoveHighlights();
18861         CommentPopDown();
18862         ToNrEvent(currentMove+1);
18863 }
18864
18865 void
18866 LoadTheme ()
18867 {
18868     char *p, *q, buf[MSG_SIZ];
18869     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18870         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18871         ParseArgsFromString(buf);
18872         ActivateTheme(TRUE); // also redo colors
18873         return;
18874     }
18875     p = nickName;
18876     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18877     {
18878         int len;
18879         q = appData.themeNames;
18880         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18881       if(appData.useBitmaps) {
18882         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18883                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18884                 appData.liteBackTextureMode,
18885                 appData.darkBackTextureMode );
18886       } else {
18887         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18888                 Col2Text(2),   // lightSquareColor
18889                 Col2Text(3) ); // darkSquareColor
18890       }
18891       if(appData.useBorder) {
18892         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18893                 appData.border);
18894       } else {
18895         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18896       }
18897       if(appData.useFont) {
18898         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18899                 appData.renderPiecesWithFont,
18900                 appData.fontToPieceTable,
18901                 Col2Text(9),    // appData.fontBackColorWhite
18902                 Col2Text(10) ); // appData.fontForeColorBlack
18903       } else {
18904         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18905                 appData.pieceDirectory);
18906         if(!appData.pieceDirectory[0])
18907           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18908                 Col2Text(0),   // whitePieceColor
18909                 Col2Text(1) ); // blackPieceColor
18910       }
18911       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18912                 Col2Text(4),   // highlightSquareColor
18913                 Col2Text(5) ); // premoveHighlightColor
18914         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18915         if(insert != q) insert[-1] = NULLCHAR;
18916         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18917         if(q)   free(q);
18918     }
18919     ActivateTheme(FALSE);
18920 }