Allow promotion on two-leg move
[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         static char c[2];
5149         *c = m[7]; // promoChar
5150         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
5151           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5152                                                m[2], m[3] - '0',
5153                                                m[5], m[6] - '0',
5154                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5155         else
5156           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5157                                                m[5], m[6] - '0',
5158                                                m[5], m[6] - '0',
5159                                                m[2], m[3] - '0', c);
5160           SendToProgram(buf, cps);
5161       } else
5162       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5163         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5164           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5165           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5166                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5167         } else
5168           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5169                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5170         SendToProgram(buf, cps);
5171       }
5172       else SendToProgram(moveList[moveNum], cps);
5173       /* End of additions by Tord */
5174     }
5175
5176     /* [HGM] setting up the opening has brought engine in force mode! */
5177     /*       Send 'go' if we are in a mode where machine should play. */
5178     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5179         (gameMode == TwoMachinesPlay   ||
5180 #if ZIPPY
5181          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5182 #endif
5183          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5184         SendToProgram("go\n", cps);
5185   if (appData.debugMode) {
5186     fprintf(debugFP, "(extra)\n");
5187   }
5188     }
5189     setboardSpoiledMachineBlack = 0;
5190 }
5191
5192 void
5193 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5194 {
5195     char user_move[MSG_SIZ];
5196     char suffix[4];
5197
5198     if(gameInfo.variant == VariantSChess && promoChar) {
5199         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5200         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5201     } else suffix[0] = NULLCHAR;
5202
5203     switch (moveType) {
5204       default:
5205         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5206                 (int)moveType, fromX, fromY, toX, toY);
5207         DisplayError(user_move + strlen("say "), 0);
5208         break;
5209       case WhiteKingSideCastle:
5210       case BlackKingSideCastle:
5211       case WhiteQueenSideCastleWild:
5212       case BlackQueenSideCastleWild:
5213       /* PUSH Fabien */
5214       case WhiteHSideCastleFR:
5215       case BlackHSideCastleFR:
5216       /* POP Fabien */
5217         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5218         break;
5219       case WhiteQueenSideCastle:
5220       case BlackQueenSideCastle:
5221       case WhiteKingSideCastleWild:
5222       case BlackKingSideCastleWild:
5223       /* PUSH Fabien */
5224       case WhiteASideCastleFR:
5225       case BlackASideCastleFR:
5226       /* POP Fabien */
5227         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5228         break;
5229       case WhiteNonPromotion:
5230       case BlackNonPromotion:
5231         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5232         break;
5233       case WhitePromotion:
5234       case BlackPromotion:
5235         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5236            gameInfo.variant == VariantMakruk)
5237           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5238                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5239                 PieceToChar(WhiteFerz));
5240         else if(gameInfo.variant == VariantGreat)
5241           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5242                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5243                 PieceToChar(WhiteMan));
5244         else
5245           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5246                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5247                 promoChar);
5248         break;
5249       case WhiteDrop:
5250       case BlackDrop:
5251       drop:
5252         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5253                  ToUpper(PieceToChar((ChessSquare) fromX)),
5254                  AAA + toX, ONE + toY);
5255         break;
5256       case IllegalMove:  /* could be a variant we don't quite understand */
5257         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5258       case NormalMove:
5259       case WhiteCapturesEnPassant:
5260       case BlackCapturesEnPassant:
5261         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5262                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5263         break;
5264     }
5265     SendToICS(user_move);
5266     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5267         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5268 }
5269
5270 void
5271 UploadGameEvent ()
5272 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5273     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5274     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5275     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5276       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5277       return;
5278     }
5279     if(gameMode != IcsExamining) { // is this ever not the case?
5280         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5281
5282         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5283           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5284         } else { // on FICS we must first go to general examine mode
5285           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5286         }
5287         if(gameInfo.variant != VariantNormal) {
5288             // try figure out wild number, as xboard names are not always valid on ICS
5289             for(i=1; i<=36; i++) {
5290               snprintf(buf, MSG_SIZ, "wild/%d", i);
5291                 if(StringToVariant(buf) == gameInfo.variant) break;
5292             }
5293             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5294             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5295             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5296         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5297         SendToICS(ics_prefix);
5298         SendToICS(buf);
5299         if(startedFromSetupPosition || backwardMostMove != 0) {
5300           fen = PositionToFEN(backwardMostMove, NULL, 1);
5301           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5302             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5303             SendToICS(buf);
5304           } else { // FICS: everything has to set by separate bsetup commands
5305             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5306             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5307             SendToICS(buf);
5308             if(!WhiteOnMove(backwardMostMove)) {
5309                 SendToICS("bsetup tomove black\n");
5310             }
5311             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5312             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5313             SendToICS(buf);
5314             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5315             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5316             SendToICS(buf);
5317             i = boards[backwardMostMove][EP_STATUS];
5318             if(i >= 0) { // set e.p.
5319               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5320                 SendToICS(buf);
5321             }
5322             bsetup++;
5323           }
5324         }
5325       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5326             SendToICS("bsetup done\n"); // switch to normal examining.
5327     }
5328     for(i = backwardMostMove; i<last; i++) {
5329         char buf[20];
5330         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5331         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5332             int len = strlen(moveList[i]);
5333             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5334             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5335         }
5336         SendToICS(buf);
5337     }
5338     SendToICS(ics_prefix);
5339     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5340 }
5341
5342 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5343 int legNr = 1;
5344
5345 void
5346 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5347 {
5348     if (rf == DROP_RANK) {
5349       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5350       sprintf(move, "%c@%c%c\n",
5351                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5352     } else {
5353         if (promoChar == 'x' || promoChar == NULLCHAR) {
5354           sprintf(move, "%c%c%c%c\n",
5355                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5356           if(killX >= 0 && killY >= 0) {
5357             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5358             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + killX, ONE + killY);
5359           }
5360         } else {
5361             sprintf(move, "%c%c%c%c%c\n",
5362                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5363           if(killX >= 0 && killY >= 0) {
5364             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5365             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + killX, ONE + killY, promoChar);
5366           }
5367         }
5368     }
5369 }
5370
5371 void
5372 ProcessICSInitScript (FILE *f)
5373 {
5374     char buf[MSG_SIZ];
5375
5376     while (fgets(buf, MSG_SIZ, f)) {
5377         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5378     }
5379
5380     fclose(f);
5381 }
5382
5383
5384 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5385 int dragging;
5386 static ClickType lastClickType;
5387
5388 int
5389 PieceInString (char *s, ChessSquare piece)
5390 {
5391   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5392   while((p = strchr(s, ID))) {
5393     if(!suffix || p[1] == suffix) return TRUE;
5394     s = p;
5395   }
5396   return FALSE;
5397 }
5398
5399 int
5400 Partner (ChessSquare *p)
5401 { // change piece into promotion partner if one shogi-promotes to the other
5402   ChessSquare partner = promoPartner[*p];
5403   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5404   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5405   *p = partner;
5406   return 1;
5407 }
5408
5409 void
5410 Sweep (int step)
5411 {
5412     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5413     static int toggleFlag;
5414     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5415     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5416     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5417     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5418     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5419     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5420     do {
5421         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5422         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5423         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5424         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5425         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5426         if(!step) step = -1;
5427     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5428             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5429             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5430             promoSweep == pawn ||
5431             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5432             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5433     if(toX >= 0) {
5434         int victim = boards[currentMove][toY][toX];
5435         boards[currentMove][toY][toX] = promoSweep;
5436         DrawPosition(FALSE, boards[currentMove]);
5437         boards[currentMove][toY][toX] = victim;
5438     } else
5439     ChangeDragPiece(promoSweep);
5440 }
5441
5442 int
5443 PromoScroll (int x, int y)
5444 {
5445   int step = 0;
5446
5447   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5448   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5449   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5450   if(!step) return FALSE;
5451   lastX = x; lastY = y;
5452   if((promoSweep < BlackPawn) == flipView) step = -step;
5453   if(step > 0) selectFlag = 1;
5454   if(!selectFlag) Sweep(step);
5455   return FALSE;
5456 }
5457
5458 void
5459 NextPiece (int step)
5460 {
5461     ChessSquare piece = boards[currentMove][toY][toX];
5462     do {
5463         pieceSweep -= step;
5464         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5465         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5466         if(!step) step = -1;
5467     } while(PieceToChar(pieceSweep) == '.');
5468     boards[currentMove][toY][toX] = pieceSweep;
5469     DrawPosition(FALSE, boards[currentMove]);
5470     boards[currentMove][toY][toX] = piece;
5471 }
5472 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5473 void
5474 AlphaRank (char *move, int n)
5475 {
5476 //    char *p = move, c; int x, y;
5477
5478     if (appData.debugMode) {
5479         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5480     }
5481
5482     if(move[1]=='*' &&
5483        move[2]>='0' && move[2]<='9' &&
5484        move[3]>='a' && move[3]<='x'    ) {
5485         move[1] = '@';
5486         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5487         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5488     } else
5489     if(move[0]>='0' && move[0]<='9' &&
5490        move[1]>='a' && move[1]<='x' &&
5491        move[2]>='0' && move[2]<='9' &&
5492        move[3]>='a' && move[3]<='x'    ) {
5493         /* input move, Shogi -> normal */
5494         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5495         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5496         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5497         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5498     } else
5499     if(move[1]=='@' &&
5500        move[3]>='0' && move[3]<='9' &&
5501        move[2]>='a' && move[2]<='x'    ) {
5502         move[1] = '*';
5503         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5504         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5505     } else
5506     if(
5507        move[0]>='a' && move[0]<='x' &&
5508        move[3]>='0' && move[3]<='9' &&
5509        move[2]>='a' && move[2]<='x'    ) {
5510          /* output move, normal -> Shogi */
5511         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5512         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5513         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5514         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5515         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5516     }
5517     if (appData.debugMode) {
5518         fprintf(debugFP, "   out = '%s'\n", move);
5519     }
5520 }
5521
5522 char yy_textstr[8000];
5523
5524 /* Parser for moves from gnuchess, ICS, or user typein box */
5525 Boolean
5526 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5527 {
5528     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5529
5530     switch (*moveType) {
5531       case WhitePromotion:
5532       case BlackPromotion:
5533       case WhiteNonPromotion:
5534       case BlackNonPromotion:
5535       case NormalMove:
5536       case FirstLeg:
5537       case WhiteCapturesEnPassant:
5538       case BlackCapturesEnPassant:
5539       case WhiteKingSideCastle:
5540       case WhiteQueenSideCastle:
5541       case BlackKingSideCastle:
5542       case BlackQueenSideCastle:
5543       case WhiteKingSideCastleWild:
5544       case WhiteQueenSideCastleWild:
5545       case BlackKingSideCastleWild:
5546       case BlackQueenSideCastleWild:
5547       /* Code added by Tord: */
5548       case WhiteHSideCastleFR:
5549       case WhiteASideCastleFR:
5550       case BlackHSideCastleFR:
5551       case BlackASideCastleFR:
5552       /* End of code added by Tord */
5553       case IllegalMove:         /* bug or odd chess variant */
5554         if(currentMoveString[1] == '@') { // illegal drop
5555           *fromX = WhiteOnMove(moveNum) ?
5556             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5557             (int) CharToPiece(ToLower(currentMoveString[0]));
5558           goto drop;
5559         }
5560         *fromX = currentMoveString[0] - AAA;
5561         *fromY = currentMoveString[1] - ONE;
5562         *toX = currentMoveString[2] - AAA;
5563         *toY = currentMoveString[3] - ONE;
5564         *promoChar = currentMoveString[4];
5565         if(*promoChar == ';') *promoChar = currentMoveString[7];
5566         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5567             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5568     if (appData.debugMode) {
5569         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5570     }
5571             *fromX = *fromY = *toX = *toY = 0;
5572             return FALSE;
5573         }
5574         if (appData.testLegality) {
5575           return (*moveType != IllegalMove);
5576         } else {
5577           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5578                          // [HGM] lion: if this is a double move we are less critical
5579                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5580         }
5581
5582       case WhiteDrop:
5583       case BlackDrop:
5584         *fromX = *moveType == WhiteDrop ?
5585           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5586           (int) CharToPiece(ToLower(currentMoveString[0]));
5587       drop:
5588         *fromY = DROP_RANK;
5589         *toX = currentMoveString[2] - AAA;
5590         *toY = currentMoveString[3] - ONE;
5591         *promoChar = NULLCHAR;
5592         return TRUE;
5593
5594       case AmbiguousMove:
5595       case ImpossibleMove:
5596       case EndOfFile:
5597       case ElapsedTime:
5598       case Comment:
5599       case PGNTag:
5600       case NAG:
5601       case WhiteWins:
5602       case BlackWins:
5603       case GameIsDrawn:
5604       default:
5605     if (appData.debugMode) {
5606         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5607     }
5608         /* bug? */
5609         *fromX = *fromY = *toX = *toY = 0;
5610         *promoChar = NULLCHAR;
5611         return FALSE;
5612     }
5613 }
5614
5615 Boolean pushed = FALSE;
5616 char *lastParseAttempt;
5617
5618 void
5619 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5620 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5621   int fromX, fromY, toX, toY; char promoChar;
5622   ChessMove moveType;
5623   Boolean valid;
5624   int nr = 0;
5625
5626   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5627   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5628     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5629     pushed = TRUE;
5630   }
5631   endPV = forwardMostMove;
5632   do {
5633     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5634     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5635     lastParseAttempt = pv;
5636     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5637     if(!valid && nr == 0 &&
5638        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5639         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5640         // Hande case where played move is different from leading PV move
5641         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5642         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5643         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5644         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5645           endPV += 2; // if position different, keep this
5646           moveList[endPV-1][0] = fromX + AAA;
5647           moveList[endPV-1][1] = fromY + ONE;
5648           moveList[endPV-1][2] = toX + AAA;
5649           moveList[endPV-1][3] = toY + ONE;
5650           parseList[endPV-1][0] = NULLCHAR;
5651           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5652         }
5653       }
5654     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5655     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5656     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5657     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5658         valid++; // allow comments in PV
5659         continue;
5660     }
5661     nr++;
5662     if(endPV+1 > framePtr) break; // no space, truncate
5663     if(!valid) break;
5664     endPV++;
5665     CopyBoard(boards[endPV], boards[endPV-1]);
5666     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5667     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5668     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5669     CoordsToAlgebraic(boards[endPV - 1],
5670                              PosFlags(endPV - 1),
5671                              fromY, fromX, toY, toX, promoChar,
5672                              parseList[endPV - 1]);
5673   } while(valid);
5674   if(atEnd == 2) return; // used hidden, for PV conversion
5675   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5676   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5677   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5678                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5679   DrawPosition(TRUE, boards[currentMove]);
5680 }
5681
5682 int
5683 MultiPV (ChessProgramState *cps, int kind)
5684 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5685         int i;
5686         for(i=0; i<cps->nrOptions; i++) {
5687             char *s = cps->option[i].name;
5688             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5689             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5690                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5691         }
5692         return -1;
5693 }
5694
5695 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5696 static int multi, pv_margin;
5697 static ChessProgramState *activeCps;
5698
5699 Boolean
5700 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5701 {
5702         int startPV, lineStart, origIndex = index;
5703         char *p, buf2[MSG_SIZ];
5704         ChessProgramState *cps = (pane ? &second : &first);
5705
5706         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5707         lastX = x; lastY = y;
5708         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5709         lineStart = startPV = index;
5710         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5711         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5712         index = startPV;
5713         do{ while(buf[index] && buf[index] != '\n') index++;
5714         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5715         buf[index] = 0;
5716         if(lineStart == 0 && gameMode == AnalyzeMode) {
5717             int n = 0;
5718             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5719             if(n == 0) { // click not on "fewer" or "more"
5720                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5721                     pv_margin = cps->option[multi].value;
5722                     activeCps = cps; // non-null signals margin adjustment
5723                 }
5724             } else if((multi = MultiPV(cps, 1)) >= 0) {
5725                 n += cps->option[multi].value; if(n < 1) n = 1;
5726                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5727                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5728                 cps->option[multi].value = n;
5729                 *start = *end = 0;
5730                 return FALSE;
5731             }
5732         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5733                 ExcludeClick(origIndex - lineStart);
5734                 return FALSE;
5735         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5736                 Collapse(origIndex - lineStart);
5737                 return FALSE;
5738         }
5739         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5740         *start = startPV; *end = index-1;
5741         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5742         return TRUE;
5743 }
5744
5745 char *
5746 PvToSAN (char *pv)
5747 {
5748         static char buf[10*MSG_SIZ];
5749         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5750         *buf = NULLCHAR;
5751         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5752         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5753         for(i = forwardMostMove; i<endPV; i++){
5754             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5755             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5756             k += strlen(buf+k);
5757         }
5758         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5759         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5760         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5761         endPV = savedEnd;
5762         return buf;
5763 }
5764
5765 Boolean
5766 LoadPV (int x, int y)
5767 { // called on right mouse click to load PV
5768   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5769   lastX = x; lastY = y;
5770   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5771   extendGame = FALSE;
5772   return TRUE;
5773 }
5774
5775 void
5776 UnLoadPV ()
5777 {
5778   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5779   if(activeCps) {
5780     if(pv_margin != activeCps->option[multi].value) {
5781       char buf[MSG_SIZ];
5782       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5783       SendToProgram(buf, activeCps);
5784       activeCps->option[multi].value = pv_margin;
5785     }
5786     activeCps = NULL;
5787     return;
5788   }
5789   if(endPV < 0) return;
5790   if(appData.autoCopyPV) CopyFENToClipboard();
5791   endPV = -1;
5792   if(extendGame && currentMove > forwardMostMove) {
5793         Boolean saveAnimate = appData.animate;
5794         if(pushed) {
5795             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5796                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5797             } else storedGames--; // abandon shelved tail of original game
5798         }
5799         pushed = FALSE;
5800         forwardMostMove = currentMove;
5801         currentMove = oldFMM;
5802         appData.animate = FALSE;
5803         ToNrEvent(forwardMostMove);
5804         appData.animate = saveAnimate;
5805   }
5806   currentMove = forwardMostMove;
5807   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5808   ClearPremoveHighlights();
5809   DrawPosition(TRUE, boards[currentMove]);
5810 }
5811
5812 void
5813 MovePV (int x, int y, int h)
5814 { // step through PV based on mouse coordinates (called on mouse move)
5815   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5816
5817   if(activeCps) { // adjusting engine's multi-pv margin
5818     if(x > lastX) pv_margin++; else
5819     if(x < lastX) pv_margin -= (pv_margin > 0);
5820     if(x != lastX) {
5821       char buf[MSG_SIZ];
5822       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5823       DisplayMessage(buf, "");
5824     }
5825     lastX = x;
5826     return;
5827   }
5828   // we must somehow check if right button is still down (might be released off board!)
5829   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5830   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5831   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5832   if(!step) return;
5833   lastX = x; lastY = y;
5834
5835   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5836   if(endPV < 0) return;
5837   if(y < margin) step = 1; else
5838   if(y > h - margin) step = -1;
5839   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5840   currentMove += step;
5841   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5842   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5843                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5844   DrawPosition(FALSE, boards[currentMove]);
5845 }
5846
5847
5848 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5849 // All positions will have equal probability, but the current method will not provide a unique
5850 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5851 #define DARK 1
5852 #define LITE 2
5853 #define ANY 3
5854
5855 int squaresLeft[4];
5856 int piecesLeft[(int)BlackPawn];
5857 int seed, nrOfShuffles;
5858
5859 void
5860 GetPositionNumber ()
5861 {       // sets global variable seed
5862         int i;
5863
5864         seed = appData.defaultFrcPosition;
5865         if(seed < 0) { // randomize based on time for negative FRC position numbers
5866                 for(i=0; i<50; i++) seed += random();
5867                 seed = random() ^ random() >> 8 ^ random() << 8;
5868                 if(seed<0) seed = -seed;
5869         }
5870 }
5871
5872 int
5873 put (Board board, int pieceType, int rank, int n, int shade)
5874 // put the piece on the (n-1)-th empty squares of the given shade
5875 {
5876         int i;
5877
5878         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5879                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5880                         board[rank][i] = (ChessSquare) pieceType;
5881                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5882                         squaresLeft[ANY]--;
5883                         piecesLeft[pieceType]--;
5884                         return i;
5885                 }
5886         }
5887         return -1;
5888 }
5889
5890
5891 void
5892 AddOnePiece (Board board, int pieceType, int rank, int shade)
5893 // calculate where the next piece goes, (any empty square), and put it there
5894 {
5895         int i;
5896
5897         i = seed % squaresLeft[shade];
5898         nrOfShuffles *= squaresLeft[shade];
5899         seed /= squaresLeft[shade];
5900         put(board, pieceType, rank, i, shade);
5901 }
5902
5903 void
5904 AddTwoPieces (Board board, int pieceType, int rank)
5905 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5906 {
5907         int i, n=squaresLeft[ANY], j=n-1, k;
5908
5909         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5910         i = seed % k;  // pick one
5911         nrOfShuffles *= k;
5912         seed /= k;
5913         while(i >= j) i -= j--;
5914         j = n - 1 - j; i += j;
5915         put(board, pieceType, rank, j, ANY);
5916         put(board, pieceType, rank, i, ANY);
5917 }
5918
5919 void
5920 SetUpShuffle (Board board, int number)
5921 {
5922         int i, p, first=1;
5923
5924         GetPositionNumber(); nrOfShuffles = 1;
5925
5926         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5927         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5928         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5929
5930         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5931
5932         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5933             p = (int) board[0][i];
5934             if(p < (int) BlackPawn) piecesLeft[p] ++;
5935             board[0][i] = EmptySquare;
5936         }
5937
5938         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5939             // shuffles restricted to allow normal castling put KRR first
5940             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5941                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5942             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5943                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5944             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5945                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5946             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5947                 put(board, WhiteRook, 0, 0, ANY);
5948             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5949         }
5950
5951         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5952             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5953             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5954                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5955                 while(piecesLeft[p] >= 2) {
5956                     AddOnePiece(board, p, 0, LITE);
5957                     AddOnePiece(board, p, 0, DARK);
5958                 }
5959                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5960             }
5961
5962         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5963             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5964             // but we leave King and Rooks for last, to possibly obey FRC restriction
5965             if(p == (int)WhiteRook) continue;
5966             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5967             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5968         }
5969
5970         // now everything is placed, except perhaps King (Unicorn) and Rooks
5971
5972         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5973             // Last King gets castling rights
5974             while(piecesLeft[(int)WhiteUnicorn]) {
5975                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5976                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5977             }
5978
5979             while(piecesLeft[(int)WhiteKing]) {
5980                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5981                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5982             }
5983
5984
5985         } else {
5986             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5987             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5988         }
5989
5990         // Only Rooks can be left; simply place them all
5991         while(piecesLeft[(int)WhiteRook]) {
5992                 i = put(board, WhiteRook, 0, 0, ANY);
5993                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5994                         if(first) {
5995                                 first=0;
5996                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5997                         }
5998                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5999                 }
6000         }
6001         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6002             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6003         }
6004
6005         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6006 }
6007
6008 int
6009 ptclen (const char *s, char *escapes)
6010 {
6011     int n = 0;
6012     if(!*escapes) return strlen(s);
6013     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6014     return n;
6015 }
6016
6017 int
6018 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6019 /* [HGM] moved here from winboard.c because of its general usefulness */
6020 /*       Basically a safe strcpy that uses the last character as King */
6021 {
6022     int result = FALSE; int NrPieces;
6023     unsigned char partner[EmptySquare];
6024
6025     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6026                     && NrPieces >= 12 && !(NrPieces&1)) {
6027         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6028
6029         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6030         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6031             char *p, c=0;
6032             if(map[j] == '/') offs = WhitePBishop - i, j++;
6033             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6034             table[i+offs] = map[j++];
6035             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6036             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6037             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6038         }
6039         table[(int) WhiteKing]  = map[j++];
6040         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6041             char *p, c=0;
6042             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6043             i = WHITE_TO_BLACK ii;
6044             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6045             table[i+offs] = map[j++];
6046             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6047             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6048             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6049         }
6050         table[(int) BlackKing]  = map[j++];
6051
6052
6053         if(*escapes) { // set up promotion pairing
6054             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6055             // pieceToChar entirely filled, so we can look up specified partners
6056             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6057                 int c = table[i];
6058                 if(c == '^' || c == '-') { // has specified partner
6059                     int p;
6060                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6061                     if(c == '^') table[i] = '+';
6062                     if(p < EmptySquare) {
6063                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6064                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6065                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6066                     }
6067                 } else if(c == '*') {
6068                     table[i] = partner[i];
6069                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6070                 }
6071             }
6072         }
6073
6074         result = TRUE;
6075     }
6076
6077     return result;
6078 }
6079
6080 int
6081 SetCharTable (unsigned char *table, const char * map)
6082 {
6083     return SetCharTableEsc(table, map, "");
6084 }
6085
6086 void
6087 Prelude (Board board)
6088 {       // [HGM] superchess: random selection of exo-pieces
6089         int i, j, k; ChessSquare p;
6090         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6091
6092         GetPositionNumber(); // use FRC position number
6093
6094         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6095             SetCharTable(pieceToChar, appData.pieceToCharTable);
6096             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6097                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6098         }
6099
6100         j = seed%4;                 seed /= 4;
6101         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6102         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6103         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6104         j = seed%3 + (seed%3 >= j); seed /= 3;
6105         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6106         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6107         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6108         j = seed%3;                 seed /= 3;
6109         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6110         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6111         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6112         j = seed%2 + (seed%2 >= j); seed /= 2;
6113         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6114         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6115         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6116         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6117         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6118         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6119         put(board, exoPieces[0],    0, 0, ANY);
6120         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6121 }
6122
6123 void
6124 InitPosition (int redraw)
6125 {
6126     ChessSquare (* pieces)[BOARD_FILES];
6127     int i, j, pawnRow=1, pieceRows=1, overrule,
6128     oldx = gameInfo.boardWidth,
6129     oldy = gameInfo.boardHeight,
6130     oldh = gameInfo.holdingsWidth;
6131     static int oldv;
6132
6133     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6134
6135     /* [AS] Initialize pv info list [HGM] and game status */
6136     {
6137         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6138             pvInfoList[i].depth = 0;
6139             boards[i][EP_STATUS] = EP_NONE;
6140             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6141         }
6142
6143         initialRulePlies = 0; /* 50-move counter start */
6144
6145         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6146         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6147     }
6148
6149
6150     /* [HGM] logic here is completely changed. In stead of full positions */
6151     /* the initialized data only consist of the two backranks. The switch */
6152     /* selects which one we will use, which is than copied to the Board   */
6153     /* initialPosition, which for the rest is initialized by Pawns and    */
6154     /* empty squares. This initial position is then copied to boards[0],  */
6155     /* possibly after shuffling, so that it remains available.            */
6156
6157     gameInfo.holdingsWidth = 0; /* default board sizes */
6158     gameInfo.boardWidth    = 8;
6159     gameInfo.boardHeight   = 8;
6160     gameInfo.holdingsSize  = 0;
6161     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6162     for(i=0; i<BOARD_FILES-6; i++)
6163       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6164     initialPosition[EP_STATUS] = EP_NONE;
6165     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6166     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6167     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6168          SetCharTable(pieceNickName, appData.pieceNickNames);
6169     else SetCharTable(pieceNickName, "............");
6170     pieces = FIDEArray;
6171
6172     switch (gameInfo.variant) {
6173     case VariantFischeRandom:
6174       shuffleOpenings = TRUE;
6175       appData.fischerCastling = TRUE;
6176     default:
6177       break;
6178     case VariantShatranj:
6179       pieces = ShatranjArray;
6180       nrCastlingRights = 0;
6181       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6182       break;
6183     case VariantMakruk:
6184       pieces = makrukArray;
6185       nrCastlingRights = 0;
6186       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6187       break;
6188     case VariantASEAN:
6189       pieces = aseanArray;
6190       nrCastlingRights = 0;
6191       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6192       break;
6193     case VariantTwoKings:
6194       pieces = twoKingsArray;
6195       break;
6196     case VariantGrand:
6197       pieces = GrandArray;
6198       nrCastlingRights = 0;
6199       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6200       gameInfo.boardWidth = 10;
6201       gameInfo.boardHeight = 10;
6202       gameInfo.holdingsSize = 7;
6203       break;
6204     case VariantCapaRandom:
6205       shuffleOpenings = TRUE;
6206       appData.fischerCastling = TRUE;
6207     case VariantCapablanca:
6208       pieces = CapablancaArray;
6209       gameInfo.boardWidth = 10;
6210       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6211       break;
6212     case VariantGothic:
6213       pieces = GothicArray;
6214       gameInfo.boardWidth = 10;
6215       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6216       break;
6217     case VariantSChess:
6218       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6219       gameInfo.holdingsSize = 7;
6220       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6221       break;
6222     case VariantJanus:
6223       pieces = JanusArray;
6224       gameInfo.boardWidth = 10;
6225       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6226       nrCastlingRights = 6;
6227         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6228         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6229         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6230         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6231         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6232         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6233       break;
6234     case VariantFalcon:
6235       pieces = FalconArray;
6236       gameInfo.boardWidth = 10;
6237       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6238       break;
6239     case VariantXiangqi:
6240       pieces = XiangqiArray;
6241       gameInfo.boardWidth  = 9;
6242       gameInfo.boardHeight = 10;
6243       nrCastlingRights = 0;
6244       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6245       break;
6246     case VariantShogi:
6247       pieces = ShogiArray;
6248       gameInfo.boardWidth  = 9;
6249       gameInfo.boardHeight = 9;
6250       gameInfo.holdingsSize = 7;
6251       nrCastlingRights = 0;
6252       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6253       break;
6254     case VariantChu:
6255       pieces = ChuArray; pieceRows = 3;
6256       gameInfo.boardWidth  = 12;
6257       gameInfo.boardHeight = 12;
6258       nrCastlingRights = 0;
6259       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6260                                    "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6261       break;
6262     case VariantCourier:
6263       pieces = CourierArray;
6264       gameInfo.boardWidth  = 12;
6265       nrCastlingRights = 0;
6266       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6267       break;
6268     case VariantKnightmate:
6269       pieces = KnightmateArray;
6270       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6271       break;
6272     case VariantSpartan:
6273       pieces = SpartanArray;
6274       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6275       break;
6276     case VariantLion:
6277       pieces = lionArray;
6278       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6279       break;
6280     case VariantChuChess:
6281       pieces = ChuChessArray;
6282       gameInfo.boardWidth = 10;
6283       gameInfo.boardHeight = 10;
6284       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6285       break;
6286     case VariantFairy:
6287       pieces = fairyArray;
6288       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6289       break;
6290     case VariantGreat:
6291       pieces = GreatArray;
6292       gameInfo.boardWidth = 10;
6293       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6294       gameInfo.holdingsSize = 8;
6295       break;
6296     case VariantSuper:
6297       pieces = FIDEArray;
6298       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6299       gameInfo.holdingsSize = 8;
6300       startedFromSetupPosition = TRUE;
6301       break;
6302     case VariantCrazyhouse:
6303     case VariantBughouse:
6304       pieces = FIDEArray;
6305       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6306       gameInfo.holdingsSize = 5;
6307       break;
6308     case VariantWildCastle:
6309       pieces = FIDEArray;
6310       /* !!?shuffle with kings guaranteed to be on d or e file */
6311       shuffleOpenings = 1;
6312       break;
6313     case VariantNoCastle:
6314       pieces = FIDEArray;
6315       nrCastlingRights = 0;
6316       /* !!?unconstrained back-rank shuffle */
6317       shuffleOpenings = 1;
6318       break;
6319     }
6320
6321     overrule = 0;
6322     if(appData.NrFiles >= 0) {
6323         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6324         gameInfo.boardWidth = appData.NrFiles;
6325     }
6326     if(appData.NrRanks >= 0) {
6327         gameInfo.boardHeight = appData.NrRanks;
6328     }
6329     if(appData.holdingsSize >= 0) {
6330         i = appData.holdingsSize;
6331         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6332         gameInfo.holdingsSize = i;
6333     }
6334     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6335     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6336         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6337
6338     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6339     if(pawnRow < 1) pawnRow = 1;
6340     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6341        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6342     if(gameInfo.variant == VariantChu) pawnRow = 3;
6343
6344     /* User pieceToChar list overrules defaults */
6345     if(appData.pieceToCharTable != NULL)
6346         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6347
6348     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6349
6350         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6351             s = (ChessSquare) 0; /* account holding counts in guard band */
6352         for( i=0; i<BOARD_HEIGHT; i++ )
6353             initialPosition[i][j] = s;
6354
6355         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6356         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6357         initialPosition[pawnRow][j] = WhitePawn;
6358         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6359         if(gameInfo.variant == VariantXiangqi) {
6360             if(j&1) {
6361                 initialPosition[pawnRow][j] =
6362                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6363                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6364                    initialPosition[2][j] = WhiteCannon;
6365                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6366                 }
6367             }
6368         }
6369         if(gameInfo.variant == VariantChu) {
6370              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6371                initialPosition[pawnRow+1][j] = WhiteCobra,
6372                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6373              for(i=1; i<pieceRows; i++) {
6374                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6375                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6376              }
6377         }
6378         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6379             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6380                initialPosition[0][j] = WhiteRook;
6381                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6382             }
6383         }
6384         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6385     }
6386     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6387     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6388
6389             j=BOARD_LEFT+1;
6390             initialPosition[1][j] = WhiteBishop;
6391             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6392             j=BOARD_RGHT-2;
6393             initialPosition[1][j] = WhiteRook;
6394             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6395     }
6396
6397     if( nrCastlingRights == -1) {
6398         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6399         /*       This sets default castling rights from none to normal corners   */
6400         /* Variants with other castling rights must set them themselves above    */
6401         nrCastlingRights = 6;
6402
6403         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6404         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6405         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6406         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6407         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6408         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6409      }
6410
6411      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6412      if(gameInfo.variant == VariantGreat) { // promotion commoners
6413         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6414         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6415         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6416         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6417      }
6418      if( gameInfo.variant == VariantSChess ) {
6419       initialPosition[1][0] = BlackMarshall;
6420       initialPosition[2][0] = BlackAngel;
6421       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6422       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6423       initialPosition[1][1] = initialPosition[2][1] =
6424       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6425      }
6426   if (appData.debugMode) {
6427     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6428   }
6429     if(shuffleOpenings) {
6430         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6431         startedFromSetupPosition = TRUE;
6432     }
6433     if(startedFromPositionFile) {
6434       /* [HGM] loadPos: use PositionFile for every new game */
6435       CopyBoard(initialPosition, filePosition);
6436       for(i=0; i<nrCastlingRights; i++)
6437           initialRights[i] = filePosition[CASTLING][i];
6438       startedFromSetupPosition = TRUE;
6439     }
6440
6441     CopyBoard(boards[0], initialPosition);
6442
6443     if(oldx != gameInfo.boardWidth ||
6444        oldy != gameInfo.boardHeight ||
6445        oldv != gameInfo.variant ||
6446        oldh != gameInfo.holdingsWidth
6447                                          )
6448             InitDrawingSizes(-2 ,0);
6449
6450     oldv = gameInfo.variant;
6451     if (redraw)
6452       DrawPosition(TRUE, boards[currentMove]);
6453 }
6454
6455 void
6456 SendBoard (ChessProgramState *cps, int moveNum)
6457 {
6458     char message[MSG_SIZ];
6459
6460     if (cps->useSetboard) {
6461       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6462       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6463       SendToProgram(message, cps);
6464       free(fen);
6465
6466     } else {
6467       ChessSquare *bp;
6468       int i, j, left=0, right=BOARD_WIDTH;
6469       /* Kludge to set black to move, avoiding the troublesome and now
6470        * deprecated "black" command.
6471        */
6472       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6473         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6474
6475       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6476
6477       SendToProgram("edit\n", cps);
6478       SendToProgram("#\n", cps);
6479       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6480         bp = &boards[moveNum][i][left];
6481         for (j = left; j < right; j++, bp++) {
6482           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6483           if ((int) *bp < (int) BlackPawn) {
6484             if(j == BOARD_RGHT+1)
6485                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6486             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6487             if(message[0] == '+' || message[0] == '~') {
6488               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6489                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6490                         AAA + j, ONE + i - '0');
6491             }
6492             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6493                 message[1] = BOARD_RGHT   - 1 - j + '1';
6494                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6495             }
6496             SendToProgram(message, cps);
6497           }
6498         }
6499       }
6500
6501       SendToProgram("c\n", cps);
6502       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6503         bp = &boards[moveNum][i][left];
6504         for (j = left; j < right; j++, bp++) {
6505           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6506           if (((int) *bp != (int) EmptySquare)
6507               && ((int) *bp >= (int) BlackPawn)) {
6508             if(j == BOARD_LEFT-2)
6509                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6510             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6511                     AAA + j, ONE + i - '0');
6512             if(message[0] == '+' || message[0] == '~') {
6513               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6514                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6515                         AAA + j, ONE + i - '0');
6516             }
6517             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6518                 message[1] = BOARD_RGHT   - 1 - j + '1';
6519                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6520             }
6521             SendToProgram(message, cps);
6522           }
6523         }
6524       }
6525
6526       SendToProgram(".\n", cps);
6527     }
6528     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6529 }
6530
6531 char exclusionHeader[MSG_SIZ];
6532 int exCnt, excludePtr;
6533 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6534 static Exclusion excluTab[200];
6535 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6536
6537 static void
6538 WriteMap (int s)
6539 {
6540     int j;
6541     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6542     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6543 }
6544
6545 static void
6546 ClearMap ()
6547 {
6548     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6549     excludePtr = 24; exCnt = 0;
6550     WriteMap(0);
6551 }
6552
6553 static void
6554 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6555 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6556     char buf[2*MOVE_LEN], *p;
6557     Exclusion *e = excluTab;
6558     int i;
6559     for(i=0; i<exCnt; i++)
6560         if(e[i].ff == fromX && e[i].fr == fromY &&
6561            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6562     if(i == exCnt) { // was not in exclude list; add it
6563         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6564         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6565             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6566             return; // abort
6567         }
6568         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6569         excludePtr++; e[i].mark = excludePtr++;
6570         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6571         exCnt++;
6572     }
6573     exclusionHeader[e[i].mark] = state;
6574 }
6575
6576 static int
6577 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6578 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6579     char buf[MSG_SIZ];
6580     int j, k;
6581     ChessMove moveType;
6582     if((signed char)promoChar == -1) { // kludge to indicate best move
6583         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6584             return 1; // if unparsable, abort
6585     }
6586     // update exclusion map (resolving toggle by consulting existing state)
6587     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6588     j = k%8; k >>= 3;
6589     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6590     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6591          excludeMap[k] |=   1<<j;
6592     else excludeMap[k] &= ~(1<<j);
6593     // update header
6594     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6595     // inform engine
6596     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6597     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6598     SendToBoth(buf);
6599     return (state == '+');
6600 }
6601
6602 static void
6603 ExcludeClick (int index)
6604 {
6605     int i, j;
6606     Exclusion *e = excluTab;
6607     if(index < 25) { // none, best or tail clicked
6608         if(index < 13) { // none: include all
6609             WriteMap(0); // clear map
6610             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6611             SendToBoth("include all\n"); // and inform engine
6612         } else if(index > 18) { // tail
6613             if(exclusionHeader[19] == '-') { // tail was excluded
6614                 SendToBoth("include all\n");
6615                 WriteMap(0); // clear map completely
6616                 // now re-exclude selected moves
6617                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6618                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6619             } else { // tail was included or in mixed state
6620                 SendToBoth("exclude all\n");
6621                 WriteMap(0xFF); // fill map completely
6622                 // now re-include selected moves
6623                 j = 0; // count them
6624                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6625                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6626                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6627             }
6628         } else { // best
6629             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6630         }
6631     } else {
6632         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6633             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6634             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6635             break;
6636         }
6637     }
6638 }
6639
6640 ChessSquare
6641 DefaultPromoChoice (int white)
6642 {
6643     ChessSquare result;
6644     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6645        gameInfo.variant == VariantMakruk)
6646         result = WhiteFerz; // no choice
6647     else if(gameInfo.variant == VariantASEAN)
6648         result = WhiteRook; // no choice
6649     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6650         result= WhiteKing; // in Suicide Q is the last thing we want
6651     else if(gameInfo.variant == VariantSpartan)
6652         result = white ? WhiteQueen : WhiteAngel;
6653     else result = WhiteQueen;
6654     if(!white) result = WHITE_TO_BLACK result;
6655     return result;
6656 }
6657
6658 static int autoQueen; // [HGM] oneclick
6659
6660 int
6661 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6662 {
6663     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6664     /* [HGM] add Shogi promotions */
6665     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6666     ChessSquare piece, partner;
6667     ChessMove moveType;
6668     Boolean premove;
6669
6670     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6671     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6672
6673     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6674       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6675         return FALSE;
6676
6677     piece = boards[currentMove][fromY][fromX];
6678     if(gameInfo.variant == VariantChu) {
6679         promotionZoneSize = BOARD_HEIGHT/3;
6680         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6681     } else if(gameInfo.variant == VariantShogi) {
6682         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6683         highestPromotingPiece = (int)WhiteAlfil;
6684     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6685         promotionZoneSize = 3;
6686     }
6687
6688     // Treat Lance as Pawn when it is not representing Amazon or Lance
6689     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6690         if(piece == WhiteLance) piece = WhitePawn; else
6691         if(piece == BlackLance) piece = BlackPawn;
6692     }
6693
6694     // next weed out all moves that do not touch the promotion zone at all
6695     if((int)piece >= BlackPawn) {
6696         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6697              return FALSE;
6698         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6699         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6700     } else {
6701         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6702            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6703         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6704              return FALSE;
6705     }
6706
6707     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6708
6709     // weed out mandatory Shogi promotions
6710     if(gameInfo.variant == VariantShogi) {
6711         if(piece >= BlackPawn) {
6712             if(toY == 0 && piece == BlackPawn ||
6713                toY == 0 && piece == BlackQueen ||
6714                toY <= 1 && piece == BlackKnight) {
6715                 *promoChoice = '+';
6716                 return FALSE;
6717             }
6718         } else {
6719             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6720                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6721                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6722                 *promoChoice = '+';
6723                 return FALSE;
6724             }
6725         }
6726     }
6727
6728     // weed out obviously illegal Pawn moves
6729     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6730         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6731         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6732         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6733         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6734         // note we are not allowed to test for valid (non-)capture, due to premove
6735     }
6736
6737     // we either have a choice what to promote to, or (in Shogi) whether to promote
6738     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6739        gameInfo.variant == VariantMakruk) {
6740         ChessSquare p=BlackFerz;  // no choice
6741         while(p < EmptySquare) {  //but make sure we use piece that exists
6742             *promoChoice = PieceToChar(p++);
6743             if(*promoChoice != '.') break;
6744         }
6745         return FALSE;
6746     }
6747     // no sense asking what we must promote to if it is going to explode...
6748     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6749         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6750         return FALSE;
6751     }
6752     // give caller the default choice even if we will not make it
6753     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6754     partner = piece; // pieces can promote if the pieceToCharTable says so
6755     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6756     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6757     if(        sweepSelect && gameInfo.variant != VariantGreat
6758                            && gameInfo.variant != VariantGrand
6759                            && gameInfo.variant != VariantSuper) return FALSE;
6760     if(autoQueen) return FALSE; // predetermined
6761
6762     // suppress promotion popup on illegal moves that are not premoves
6763     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6764               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6765     if(appData.testLegality && !premove) {
6766         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6767                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6768         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6769         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6770             return FALSE;
6771     }
6772
6773     return TRUE;
6774 }
6775
6776 int
6777 InPalace (int row, int column)
6778 {   /* [HGM] for Xiangqi */
6779     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6780          column < (BOARD_WIDTH + 4)/2 &&
6781          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6782     return FALSE;
6783 }
6784
6785 int
6786 PieceForSquare (int x, int y)
6787 {
6788   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6789      return -1;
6790   else
6791      return boards[currentMove][y][x];
6792 }
6793
6794 int
6795 OKToStartUserMove (int x, int y)
6796 {
6797     ChessSquare from_piece;
6798     int white_piece;
6799
6800     if (matchMode) return FALSE;
6801     if (gameMode == EditPosition) return TRUE;
6802
6803     if (x >= 0 && y >= 0)
6804       from_piece = boards[currentMove][y][x];
6805     else
6806       from_piece = EmptySquare;
6807
6808     if (from_piece == EmptySquare) return FALSE;
6809
6810     white_piece = (int)from_piece >= (int)WhitePawn &&
6811       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6812
6813     switch (gameMode) {
6814       case AnalyzeFile:
6815       case TwoMachinesPlay:
6816       case EndOfGame:
6817         return FALSE;
6818
6819       case IcsObserving:
6820       case IcsIdle:
6821         return FALSE;
6822
6823       case MachinePlaysWhite:
6824       case IcsPlayingBlack:
6825         if (appData.zippyPlay) return FALSE;
6826         if (white_piece) {
6827             DisplayMoveError(_("You are playing Black"));
6828             return FALSE;
6829         }
6830         break;
6831
6832       case MachinePlaysBlack:
6833       case IcsPlayingWhite:
6834         if (appData.zippyPlay) return FALSE;
6835         if (!white_piece) {
6836             DisplayMoveError(_("You are playing White"));
6837             return FALSE;
6838         }
6839         break;
6840
6841       case PlayFromGameFile:
6842             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6843       case EditGame:
6844         if (!white_piece && WhiteOnMove(currentMove)) {
6845             DisplayMoveError(_("It is White's turn"));
6846             return FALSE;
6847         }
6848         if (white_piece && !WhiteOnMove(currentMove)) {
6849             DisplayMoveError(_("It is Black's turn"));
6850             return FALSE;
6851         }
6852         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6853             /* Editing correspondence game history */
6854             /* Could disallow this or prompt for confirmation */
6855             cmailOldMove = -1;
6856         }
6857         break;
6858
6859       case BeginningOfGame:
6860         if (appData.icsActive) return FALSE;
6861         if (!appData.noChessProgram) {
6862             if (!white_piece) {
6863                 DisplayMoveError(_("You are playing White"));
6864                 return FALSE;
6865             }
6866         }
6867         break;
6868
6869       case Training:
6870         if (!white_piece && WhiteOnMove(currentMove)) {
6871             DisplayMoveError(_("It is White's turn"));
6872             return FALSE;
6873         }
6874         if (white_piece && !WhiteOnMove(currentMove)) {
6875             DisplayMoveError(_("It is Black's turn"));
6876             return FALSE;
6877         }
6878         break;
6879
6880       default:
6881       case IcsExamining:
6882         break;
6883     }
6884     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6885         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6886         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6887         && gameMode != AnalyzeFile && gameMode != Training) {
6888         DisplayMoveError(_("Displayed position is not current"));
6889         return FALSE;
6890     }
6891     return TRUE;
6892 }
6893
6894 Boolean
6895 OnlyMove (int *x, int *y, Boolean captures)
6896 {
6897     DisambiguateClosure cl;
6898     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6899     switch(gameMode) {
6900       case MachinePlaysBlack:
6901       case IcsPlayingWhite:
6902       case BeginningOfGame:
6903         if(!WhiteOnMove(currentMove)) return FALSE;
6904         break;
6905       case MachinePlaysWhite:
6906       case IcsPlayingBlack:
6907         if(WhiteOnMove(currentMove)) return FALSE;
6908         break;
6909       case EditGame:
6910         break;
6911       default:
6912         return FALSE;
6913     }
6914     cl.pieceIn = EmptySquare;
6915     cl.rfIn = *y;
6916     cl.ffIn = *x;
6917     cl.rtIn = -1;
6918     cl.ftIn = -1;
6919     cl.promoCharIn = NULLCHAR;
6920     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6921     if( cl.kind == NormalMove ||
6922         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6923         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6924         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6925       fromX = cl.ff;
6926       fromY = cl.rf;
6927       *x = cl.ft;
6928       *y = cl.rt;
6929       return TRUE;
6930     }
6931     if(cl.kind != ImpossibleMove) return FALSE;
6932     cl.pieceIn = EmptySquare;
6933     cl.rfIn = -1;
6934     cl.ffIn = -1;
6935     cl.rtIn = *y;
6936     cl.ftIn = *x;
6937     cl.promoCharIn = NULLCHAR;
6938     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6939     if( cl.kind == NormalMove ||
6940         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6941         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6942         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6943       fromX = cl.ff;
6944       fromY = cl.rf;
6945       *x = cl.ft;
6946       *y = cl.rt;
6947       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6948       return TRUE;
6949     }
6950     return FALSE;
6951 }
6952
6953 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6954 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6955 int lastLoadGameUseList = FALSE;
6956 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6957 ChessMove lastLoadGameStart = EndOfFile;
6958 int doubleClick;
6959 Boolean addToBookFlag;
6960
6961 void
6962 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6963 {
6964     ChessMove moveType;
6965     ChessSquare pup;
6966     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6967
6968     /* Check if the user is playing in turn.  This is complicated because we
6969        let the user "pick up" a piece before it is his turn.  So the piece he
6970        tried to pick up may have been captured by the time he puts it down!
6971        Therefore we use the color the user is supposed to be playing in this
6972        test, not the color of the piece that is currently on the starting
6973        square---except in EditGame mode, where the user is playing both
6974        sides; fortunately there the capture race can't happen.  (It can
6975        now happen in IcsExamining mode, but that's just too bad.  The user
6976        will get a somewhat confusing message in that case.)
6977        */
6978
6979     switch (gameMode) {
6980       case AnalyzeFile:
6981       case TwoMachinesPlay:
6982       case EndOfGame:
6983       case IcsObserving:
6984       case IcsIdle:
6985         /* We switched into a game mode where moves are not accepted,
6986            perhaps while the mouse button was down. */
6987         return;
6988
6989       case MachinePlaysWhite:
6990         /* User is moving for Black */
6991         if (WhiteOnMove(currentMove)) {
6992             DisplayMoveError(_("It is White's turn"));
6993             return;
6994         }
6995         break;
6996
6997       case MachinePlaysBlack:
6998         /* User is moving for White */
6999         if (!WhiteOnMove(currentMove)) {
7000             DisplayMoveError(_("It is Black's turn"));
7001             return;
7002         }
7003         break;
7004
7005       case PlayFromGameFile:
7006             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7007       case EditGame:
7008       case IcsExamining:
7009       case BeginningOfGame:
7010       case AnalyzeMode:
7011       case Training:
7012         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7013         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7014             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7015             /* User is moving for Black */
7016             if (WhiteOnMove(currentMove)) {
7017                 DisplayMoveError(_("It is White's turn"));
7018                 return;
7019             }
7020         } else {
7021             /* User is moving for White */
7022             if (!WhiteOnMove(currentMove)) {
7023                 DisplayMoveError(_("It is Black's turn"));
7024                 return;
7025             }
7026         }
7027         break;
7028
7029       case IcsPlayingBlack:
7030         /* User is moving for Black */
7031         if (WhiteOnMove(currentMove)) {
7032             if (!appData.premove) {
7033                 DisplayMoveError(_("It is White's turn"));
7034             } else if (toX >= 0 && toY >= 0) {
7035                 premoveToX = toX;
7036                 premoveToY = toY;
7037                 premoveFromX = fromX;
7038                 premoveFromY = fromY;
7039                 premovePromoChar = promoChar;
7040                 gotPremove = 1;
7041                 if (appData.debugMode)
7042                     fprintf(debugFP, "Got premove: fromX %d,"
7043                             "fromY %d, toX %d, toY %d\n",
7044                             fromX, fromY, toX, toY);
7045             }
7046             return;
7047         }
7048         break;
7049
7050       case IcsPlayingWhite:
7051         /* User is moving for White */
7052         if (!WhiteOnMove(currentMove)) {
7053             if (!appData.premove) {
7054                 DisplayMoveError(_("It is Black's turn"));
7055             } else if (toX >= 0 && toY >= 0) {
7056                 premoveToX = toX;
7057                 premoveToY = toY;
7058                 premoveFromX = fromX;
7059                 premoveFromY = fromY;
7060                 premovePromoChar = promoChar;
7061                 gotPremove = 1;
7062                 if (appData.debugMode)
7063                     fprintf(debugFP, "Got premove: fromX %d,"
7064                             "fromY %d, toX %d, toY %d\n",
7065                             fromX, fromY, toX, toY);
7066             }
7067             return;
7068         }
7069         break;
7070
7071       default:
7072         break;
7073
7074       case EditPosition:
7075         /* EditPosition, empty square, or different color piece;
7076            click-click move is possible */
7077         if (toX == -2 || toY == -2) {
7078             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7079             DrawPosition(FALSE, boards[currentMove]);
7080             return;
7081         } else if (toX >= 0 && toY >= 0) {
7082             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7083                 ChessSquare p = boards[0][rf][ff];
7084                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7085                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); 
7086             }
7087             boards[0][toY][toX] = boards[0][fromY][fromX];
7088             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7089                 if(boards[0][fromY][0] != EmptySquare) {
7090                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7091                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7092                 }
7093             } else
7094             if(fromX == BOARD_RGHT+1) {
7095                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7096                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7097                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7098                 }
7099             } else
7100             boards[0][fromY][fromX] = gatingPiece;
7101             ClearHighlights();
7102             DrawPosition(FALSE, boards[currentMove]);
7103             return;
7104         }
7105         return;
7106     }
7107
7108     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7109     pup = boards[currentMove][toY][toX];
7110
7111     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7112     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7113          if( pup != EmptySquare ) return;
7114          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7115            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7116                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7117            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7118            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7119            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7120            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7121          fromY = DROP_RANK;
7122     }
7123
7124     /* [HGM] always test for legality, to get promotion info */
7125     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7126                                          fromY, fromX, toY, toX, promoChar);
7127
7128     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7129
7130     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7131
7132     /* [HGM] but possibly ignore an IllegalMove result */
7133     if (appData.testLegality) {
7134         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7135             DisplayMoveError(_("Illegal move"));
7136             return;
7137         }
7138     }
7139
7140     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7141         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7142              ClearPremoveHighlights(); // was included
7143         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7144         return;
7145     }
7146
7147     if(addToBookFlag) { // adding moves to book
7148         char buf[MSG_SIZ], move[MSG_SIZ];
7149         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7150         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7151                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7152         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7153         AddBookMove(buf);
7154         addToBookFlag = FALSE;
7155         ClearHighlights();
7156         return;
7157     }
7158
7159     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7160 }
7161
7162 /* Common tail of UserMoveEvent and DropMenuEvent */
7163 int
7164 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7165 {
7166     char *bookHit = 0;
7167
7168     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7169         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7170         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7171         if(WhiteOnMove(currentMove)) {
7172             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7173         } else {
7174             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7175         }
7176     }
7177
7178     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7179        move type in caller when we know the move is a legal promotion */
7180     if(moveType == NormalMove && promoChar)
7181         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7182
7183     /* [HGM] <popupFix> The following if has been moved here from
7184        UserMoveEvent(). Because it seemed to belong here (why not allow
7185        piece drops in training games?), and because it can only be
7186        performed after it is known to what we promote. */
7187     if (gameMode == Training) {
7188       /* compare the move played on the board to the next move in the
7189        * game. If they match, display the move and the opponent's response.
7190        * If they don't match, display an error message.
7191        */
7192       int saveAnimate;
7193       Board testBoard;
7194       CopyBoard(testBoard, boards[currentMove]);
7195       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7196
7197       if (CompareBoards(testBoard, boards[currentMove+1])) {
7198         ForwardInner(currentMove+1);
7199
7200         /* Autoplay the opponent's response.
7201          * if appData.animate was TRUE when Training mode was entered,
7202          * the response will be animated.
7203          */
7204         saveAnimate = appData.animate;
7205         appData.animate = animateTraining;
7206         ForwardInner(currentMove+1);
7207         appData.animate = saveAnimate;
7208
7209         /* check for the end of the game */
7210         if (currentMove >= forwardMostMove) {
7211           gameMode = PlayFromGameFile;
7212           ModeHighlight();
7213           SetTrainingModeOff();
7214           DisplayInformation(_("End of game"));
7215         }
7216       } else {
7217         DisplayError(_("Incorrect move"), 0);
7218       }
7219       return 1;
7220     }
7221
7222   /* Ok, now we know that the move is good, so we can kill
7223      the previous line in Analysis Mode */
7224   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7225                                 && currentMove < forwardMostMove) {
7226     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7227     else forwardMostMove = currentMove;
7228   }
7229
7230   ClearMap();
7231
7232   /* If we need the chess program but it's dead, restart it */
7233   ResurrectChessProgram();
7234
7235   /* A user move restarts a paused game*/
7236   if (pausing)
7237     PauseEvent();
7238
7239   thinkOutput[0] = NULLCHAR;
7240
7241   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7242
7243   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7244     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7245     return 1;
7246   }
7247
7248   if (gameMode == BeginningOfGame) {
7249     if (appData.noChessProgram) {
7250       gameMode = EditGame;
7251       SetGameInfo();
7252     } else {
7253       char buf[MSG_SIZ];
7254       gameMode = MachinePlaysBlack;
7255       StartClocks();
7256       SetGameInfo();
7257       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7258       DisplayTitle(buf);
7259       if (first.sendName) {
7260         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7261         SendToProgram(buf, &first);
7262       }
7263       StartClocks();
7264     }
7265     ModeHighlight();
7266   }
7267
7268   /* Relay move to ICS or chess engine */
7269   if (appData.icsActive) {
7270     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7271         gameMode == IcsExamining) {
7272       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7273         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7274         SendToICS("draw ");
7275         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7276       }
7277       // also send plain move, in case ICS does not understand atomic claims
7278       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7279       ics_user_moved = 1;
7280     }
7281   } else {
7282     if (first.sendTime && (gameMode == BeginningOfGame ||
7283                            gameMode == MachinePlaysWhite ||
7284                            gameMode == MachinePlaysBlack)) {
7285       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7286     }
7287     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7288          // [HGM] book: if program might be playing, let it use book
7289         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7290         first.maybeThinking = TRUE;
7291     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7292         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7293         SendBoard(&first, currentMove+1);
7294         if(second.analyzing) {
7295             if(!second.useSetboard) SendToProgram("undo\n", &second);
7296             SendBoard(&second, currentMove+1);
7297         }
7298     } else {
7299         SendMoveToProgram(forwardMostMove-1, &first);
7300         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7301     }
7302     if (currentMove == cmailOldMove + 1) {
7303       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7304     }
7305   }
7306
7307   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7308
7309   switch (gameMode) {
7310   case EditGame:
7311     if(appData.testLegality)
7312     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7313     case MT_NONE:
7314     case MT_CHECK:
7315       break;
7316     case MT_CHECKMATE:
7317     case MT_STAINMATE:
7318       if (WhiteOnMove(currentMove)) {
7319         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7320       } else {
7321         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7322       }
7323       break;
7324     case MT_STALEMATE:
7325       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7326       break;
7327     }
7328     break;
7329
7330   case MachinePlaysBlack:
7331   case MachinePlaysWhite:
7332     /* disable certain menu options while machine is thinking */
7333     SetMachineThinkingEnables();
7334     break;
7335
7336   default:
7337     break;
7338   }
7339
7340   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7341   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7342
7343   if(bookHit) { // [HGM] book: simulate book reply
7344         static char bookMove[MSG_SIZ]; // a bit generous?
7345
7346         programStats.nodes = programStats.depth = programStats.time =
7347         programStats.score = programStats.got_only_move = 0;
7348         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7349
7350         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7351         strcat(bookMove, bookHit);
7352         HandleMachineMove(bookMove, &first);
7353   }
7354   return 1;
7355 }
7356
7357 void
7358 MarkByFEN(char *fen)
7359 {
7360         int r, f;
7361         if(!appData.markers || !appData.highlightDragging) return;
7362         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7363         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7364         while(*fen) {
7365             int s = 0;
7366             marker[r][f] = 0;
7367             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7368             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7369             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7370             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7371             if(*fen == 'T') marker[r][f++] = 0; else
7372             if(*fen == 'Y') marker[r][f++] = 1; else
7373             if(*fen == 'G') marker[r][f++] = 3; else
7374             if(*fen == 'B') marker[r][f++] = 4; else
7375             if(*fen == 'C') marker[r][f++] = 5; else
7376             if(*fen == 'M') marker[r][f++] = 6; else
7377             if(*fen == 'W') marker[r][f++] = 7; else
7378             if(*fen == 'D') marker[r][f++] = 8; else
7379             if(*fen == 'R') marker[r][f++] = 2; else {
7380                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7381               f += s; fen -= s>0;
7382             }
7383             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7384             if(r < 0) break;
7385             fen++;
7386         }
7387         DrawPosition(TRUE, NULL);
7388 }
7389
7390 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7391
7392 void
7393 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7394 {
7395     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7396     Markers *m = (Markers *) closure;
7397     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7398         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7399                          || kind == WhiteCapturesEnPassant
7400                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 3;
7401     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7402 }
7403
7404 static int hoverSavedValid;
7405
7406 void
7407 MarkTargetSquares (int clear)
7408 {
7409   int x, y, sum=0;
7410   if(clear) { // no reason to ever suppress clearing
7411     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7412     hoverSavedValid = 0;
7413     if(!sum) return; // nothing was cleared,no redraw needed
7414   } else {
7415     int capt = 0;
7416     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7417        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7418     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7419     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7420       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7421       if(capt)
7422       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7423     }
7424   }
7425   DrawPosition(FALSE, NULL);
7426 }
7427
7428 int
7429 Explode (Board board, int fromX, int fromY, int toX, int toY)
7430 {
7431     if(gameInfo.variant == VariantAtomic &&
7432        (board[toY][toX] != EmptySquare ||                     // capture?
7433         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7434                          board[fromY][fromX] == BlackPawn   )
7435       )) {
7436         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7437         return TRUE;
7438     }
7439     return FALSE;
7440 }
7441
7442 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7443
7444 int
7445 CanPromote (ChessSquare piece, int y)
7446 {
7447         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7448         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7449         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7450         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7451            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7452            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7453          gameInfo.variant == VariantMakruk) return FALSE;
7454         return (piece == BlackPawn && y <= zone ||
7455                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7456                 piece == BlackLance && y <= zone ||
7457                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7458 }
7459
7460 void
7461 HoverEvent (int xPix, int yPix, int x, int y)
7462 {
7463         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7464         int r, f;
7465         if(!first.highlight) return;
7466         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7467         if(x == oldX && y == oldY) return; // only do something if we enter new square
7468         oldFromX = fromX; oldFromY = fromY;
7469         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7470           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7471             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7472           hoverSavedValid = 1;
7473         } else if(oldX != x || oldY != y) {
7474           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7475           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7476           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7477             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7478           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7479             char buf[MSG_SIZ];
7480             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7481             SendToProgram(buf, &first);
7482           }
7483           oldX = x; oldY = y;
7484 //        SetHighlights(fromX, fromY, x, y);
7485         }
7486 }
7487
7488 void ReportClick(char *action, int x, int y)
7489 {
7490         char buf[MSG_SIZ]; // Inform engine of what user does
7491         int r, f;
7492         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7493           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7494             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7495         if(!first.highlight || gameMode == EditPosition) return;
7496         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7497         SendToProgram(buf, &first);
7498 }
7499
7500 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7501
7502 void
7503 LeftClick (ClickType clickType, int xPix, int yPix)
7504 {
7505     int x, y;
7506     Boolean saveAnimate;
7507     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7508     char promoChoice = NULLCHAR;
7509     ChessSquare piece;
7510     static TimeMark lastClickTime, prevClickTime;
7511
7512     x = EventToSquare(xPix, BOARD_WIDTH);
7513     y = EventToSquare(yPix, BOARD_HEIGHT);
7514     if (!flipView && y >= 0) {
7515         y = BOARD_HEIGHT - 1 - y;
7516     }
7517     if (flipView && x >= 0) {
7518         x = BOARD_WIDTH - 1 - x;
7519     }
7520
7521     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7522         static int dummy;
7523         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7524         right = TRUE;
7525         return;
7526     }
7527
7528     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7529
7530     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7531
7532     if (clickType == Press) ErrorPopDown();
7533     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7534
7535     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7536         defaultPromoChoice = promoSweep;
7537         promoSweep = EmptySquare;   // terminate sweep
7538         promoDefaultAltered = TRUE;
7539         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7540     }
7541
7542     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7543         if(clickType == Release) return; // ignore upclick of click-click destination
7544         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7545         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7546         if(gameInfo.holdingsWidth &&
7547                 (WhiteOnMove(currentMove)
7548                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7549                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7550             // click in right holdings, for determining promotion piece
7551             ChessSquare p = boards[currentMove][y][x];
7552             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7553             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7554             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7555                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7556                 fromX = fromY = -1;
7557                 return;
7558             }
7559         }
7560         DrawPosition(FALSE, boards[currentMove]);
7561         return;
7562     }
7563
7564     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7565     if(clickType == Press
7566             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7567               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7568               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7569         return;
7570
7571     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7572         // could be static click on premove from-square: abort premove
7573         gotPremove = 0;
7574         ClearPremoveHighlights();
7575     }
7576
7577     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7578         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7579
7580     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7581         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7582                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7583         defaultPromoChoice = DefaultPromoChoice(side);
7584     }
7585
7586     autoQueen = appData.alwaysPromoteToQueen;
7587
7588     if (fromX == -1) {
7589       int originalY = y;
7590       gatingPiece = EmptySquare;
7591       if (clickType != Press) {
7592         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7593             DragPieceEnd(xPix, yPix); dragging = 0;
7594             DrawPosition(FALSE, NULL);
7595         }
7596         return;
7597       }
7598       doubleClick = FALSE;
7599       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7600         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7601       }
7602       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7603       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7604          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7605          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7606             /* First square */
7607             if (OKToStartUserMove(fromX, fromY)) {
7608                 second = 0;
7609                 ReportClick("lift", x, y);
7610                 MarkTargetSquares(0);
7611                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7612                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7613                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7614                     promoSweep = defaultPromoChoice;
7615                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7616                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7617                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7618                 }
7619                 if (appData.highlightDragging) {
7620                     SetHighlights(fromX, fromY, -1, -1);
7621                 } else {
7622                     ClearHighlights();
7623                 }
7624             } else fromX = fromY = -1;
7625             return;
7626         }
7627     }
7628
7629     /* fromX != -1 */
7630     if (clickType == Press && gameMode != EditPosition) {
7631         ChessSquare fromP;
7632         ChessSquare toP;
7633         int frc;
7634
7635         // ignore off-board to clicks
7636         if(y < 0 || x < 0) return;
7637
7638         /* Check if clicking again on the same color piece */
7639         fromP = boards[currentMove][fromY][fromX];
7640         toP = boards[currentMove][y][x];
7641         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7642         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7643             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7644            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7645              WhitePawn <= toP && toP <= WhiteKing &&
7646              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7647              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7648             (BlackPawn <= fromP && fromP <= BlackKing &&
7649              BlackPawn <= toP && toP <= BlackKing &&
7650              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7651              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7652             /* Clicked again on same color piece -- changed his mind */
7653             second = (x == fromX && y == fromY);
7654             killX = killY = -1;
7655             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7656                 second = FALSE; // first double-click rather than scond click
7657                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7658             }
7659             promoDefaultAltered = FALSE;
7660             MarkTargetSquares(1);
7661            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7662             if (appData.highlightDragging) {
7663                 SetHighlights(x, y, -1, -1);
7664             } else {
7665                 ClearHighlights();
7666             }
7667             if (OKToStartUserMove(x, y)) {
7668                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7669                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7670                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7671                  gatingPiece = boards[currentMove][fromY][fromX];
7672                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7673                 fromX = x;
7674                 fromY = y; dragging = 1;
7675                 if(!second) ReportClick("lift", x, y);
7676                 MarkTargetSquares(0);
7677                 DragPieceBegin(xPix, yPix, FALSE);
7678                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7679                     promoSweep = defaultPromoChoice;
7680                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7681                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7682                 }
7683             }
7684            }
7685            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7686            second = FALSE;
7687         }
7688         // ignore clicks on holdings
7689         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7690     }
7691
7692     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7693         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7694         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7695         return;
7696     }
7697
7698     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7699         DragPieceEnd(xPix, yPix); dragging = 0;
7700         if(clearFlag) {
7701             // a deferred attempt to click-click move an empty square on top of a piece
7702             boards[currentMove][y][x] = EmptySquare;
7703             ClearHighlights();
7704             DrawPosition(FALSE, boards[currentMove]);
7705             fromX = fromY = -1; clearFlag = 0;
7706             return;
7707         }
7708         if (appData.animateDragging) {
7709             /* Undo animation damage if any */
7710             DrawPosition(FALSE, NULL);
7711         }
7712         if (second) {
7713             /* Second up/down in same square; just abort move */
7714             second = 0;
7715             fromX = fromY = -1;
7716             gatingPiece = EmptySquare;
7717             MarkTargetSquares(1);
7718             ClearHighlights();
7719             gotPremove = 0;
7720             ClearPremoveHighlights();
7721         } else {
7722             /* First upclick in same square; start click-click mode */
7723             SetHighlights(x, y, -1, -1);
7724         }
7725         return;
7726     }
7727
7728     clearFlag = 0;
7729
7730     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7731        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7732         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7733         DisplayMessage(_("only marked squares are legal"),"");
7734         DrawPosition(TRUE, NULL);
7735         return; // ignore to-click
7736     }
7737
7738     /* we now have a different from- and (possibly off-board) to-square */
7739     /* Completed move */
7740     if(!sweepSelecting) {
7741         toX = x;
7742         toY = y;
7743     }
7744
7745     piece = boards[currentMove][fromY][fromX];
7746
7747     saveAnimate = appData.animate;
7748     if (clickType == Press) {
7749         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7750         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7751             // must be Edit Position mode with empty-square selected
7752             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7753             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7754             return;
7755         }
7756         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7757             return;
7758         }
7759         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7760             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7761         } else
7762         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7763         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7764           if(appData.sweepSelect) {
7765             promoSweep = defaultPromoChoice;
7766             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7767             selectFlag = 0; lastX = xPix; lastY = yPix;
7768             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7769             Sweep(0); // Pawn that is going to promote: preview promotion piece
7770             sweepSelecting = 1;
7771             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7772             MarkTargetSquares(1);
7773           }
7774           return; // promo popup appears on up-click
7775         }
7776         /* Finish clickclick move */
7777         if (appData.animate || appData.highlightLastMove) {
7778             SetHighlights(fromX, fromY, toX, toY);
7779         } else {
7780             ClearHighlights();
7781         }
7782     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7783         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7784         *promoRestrict = 0;
7785         if (appData.animate || appData.highlightLastMove) {
7786             SetHighlights(fromX, fromY, toX, toY);
7787         } else {
7788             ClearHighlights();
7789         }
7790     } else {
7791 #if 0
7792 // [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
7793         /* Finish drag move */
7794         if (appData.highlightLastMove) {
7795             SetHighlights(fromX, fromY, toX, toY);
7796         } else {
7797             ClearHighlights();
7798         }
7799 #endif
7800         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7801           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7802         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7803         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7804           dragging *= 2;            // flag button-less dragging if we are dragging
7805           MarkTargetSquares(1);
7806           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7807           else {
7808             kill2X = killX; kill2Y = killY;
7809             killX = x; killY = y;     //remeber this square as intermediate
7810             ReportClick("put", x, y); // and inform engine
7811             ReportClick("lift", x, y);
7812             MarkTargetSquares(0);
7813             return;
7814           }
7815         }
7816         DragPieceEnd(xPix, yPix); dragging = 0;
7817         /* Don't animate move and drag both */
7818         appData.animate = FALSE;
7819     }
7820
7821     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7822     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7823         ChessSquare piece = boards[currentMove][fromY][fromX];
7824         if(gameMode == EditPosition && piece != EmptySquare &&
7825            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7826             int n;
7827
7828             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7829                 n = PieceToNumber(piece - (int)BlackPawn);
7830                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7831                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7832                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7833             } else
7834             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7835                 n = PieceToNumber(piece);
7836                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7837                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7838                 boards[currentMove][n][BOARD_WIDTH-2]++;
7839             }
7840             boards[currentMove][fromY][fromX] = EmptySquare;
7841         }
7842         ClearHighlights();
7843         fromX = fromY = -1;
7844         MarkTargetSquares(1);
7845         DrawPosition(TRUE, boards[currentMove]);
7846         return;
7847     }
7848
7849     // off-board moves should not be highlighted
7850     if(x < 0 || y < 0) ClearHighlights();
7851     else ReportClick("put", x, y);
7852
7853     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7854
7855     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7856
7857     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7858         SetHighlights(fromX, fromY, toX, toY);
7859         MarkTargetSquares(1);
7860         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7861             // [HGM] super: promotion to captured piece selected from holdings
7862             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7863             promotionChoice = TRUE;
7864             // kludge follows to temporarily execute move on display, without promoting yet
7865             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7866             boards[currentMove][toY][toX] = p;
7867             DrawPosition(FALSE, boards[currentMove]);
7868             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7869             boards[currentMove][toY][toX] = q;
7870             DisplayMessage("Click in holdings to choose piece", "");
7871             return;
7872         }
7873         PromotionPopUp(promoChoice);
7874     } else {
7875         int oldMove = currentMove;
7876         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7877         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7878         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7879         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7880            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7881             DrawPosition(TRUE, boards[currentMove]);
7882         MarkTargetSquares(1);
7883         fromX = fromY = -1;
7884     }
7885     appData.animate = saveAnimate;
7886     if (appData.animate || appData.animateDragging) {
7887         /* Undo animation damage if needed */
7888         DrawPosition(FALSE, NULL);
7889     }
7890 }
7891
7892 int
7893 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7894 {   // front-end-free part taken out of PieceMenuPopup
7895     int whichMenu; int xSqr, ySqr;
7896
7897     if(seekGraphUp) { // [HGM] seekgraph
7898         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7899         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7900         return -2;
7901     }
7902
7903     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7904          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7905         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7906         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7907         if(action == Press)   {
7908             originalFlip = flipView;
7909             flipView = !flipView; // temporarily flip board to see game from partners perspective
7910             DrawPosition(TRUE, partnerBoard);
7911             DisplayMessage(partnerStatus, "");
7912             partnerUp = TRUE;
7913         } else if(action == Release) {
7914             flipView = originalFlip;
7915             DrawPosition(TRUE, boards[currentMove]);
7916             partnerUp = FALSE;
7917         }
7918         return -2;
7919     }
7920
7921     xSqr = EventToSquare(x, BOARD_WIDTH);
7922     ySqr = EventToSquare(y, BOARD_HEIGHT);
7923     if (action == Release) {
7924         if(pieceSweep != EmptySquare) {
7925             EditPositionMenuEvent(pieceSweep, toX, toY);
7926             pieceSweep = EmptySquare;
7927         } else UnLoadPV(); // [HGM] pv
7928     }
7929     if (action != Press) return -2; // return code to be ignored
7930     switch (gameMode) {
7931       case IcsExamining:
7932         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7933       case EditPosition:
7934         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7935         if (xSqr < 0 || ySqr < 0) return -1;
7936         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7937         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7938         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7939         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7940         NextPiece(0);
7941         return 2; // grab
7942       case IcsObserving:
7943         if(!appData.icsEngineAnalyze) return -1;
7944       case IcsPlayingWhite:
7945       case IcsPlayingBlack:
7946         if(!appData.zippyPlay) goto noZip;
7947       case AnalyzeMode:
7948       case AnalyzeFile:
7949       case MachinePlaysWhite:
7950       case MachinePlaysBlack:
7951       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7952         if (!appData.dropMenu) {
7953           LoadPV(x, y);
7954           return 2; // flag front-end to grab mouse events
7955         }
7956         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7957            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7958       case EditGame:
7959       noZip:
7960         if (xSqr < 0 || ySqr < 0) return -1;
7961         if (!appData.dropMenu || appData.testLegality &&
7962             gameInfo.variant != VariantBughouse &&
7963             gameInfo.variant != VariantCrazyhouse) return -1;
7964         whichMenu = 1; // drop menu
7965         break;
7966       default:
7967         return -1;
7968     }
7969
7970     if (((*fromX = xSqr) < 0) ||
7971         ((*fromY = ySqr) < 0)) {
7972         *fromX = *fromY = -1;
7973         return -1;
7974     }
7975     if (flipView)
7976       *fromX = BOARD_WIDTH - 1 - *fromX;
7977     else
7978       *fromY = BOARD_HEIGHT - 1 - *fromY;
7979
7980     return whichMenu;
7981 }
7982
7983 void
7984 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7985 {
7986 //    char * hint = lastHint;
7987     FrontEndProgramStats stats;
7988
7989     stats.which = cps == &first ? 0 : 1;
7990     stats.depth = cpstats->depth;
7991     stats.nodes = cpstats->nodes;
7992     stats.score = cpstats->score;
7993     stats.time = cpstats->time;
7994     stats.pv = cpstats->movelist;
7995     stats.hint = lastHint;
7996     stats.an_move_index = 0;
7997     stats.an_move_count = 0;
7998
7999     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8000         stats.hint = cpstats->move_name;
8001         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8002         stats.an_move_count = cpstats->nr_moves;
8003     }
8004
8005     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
8006
8007     SetProgramStats( &stats );
8008 }
8009
8010 void
8011 ClearEngineOutputPane (int which)
8012 {
8013     static FrontEndProgramStats dummyStats;
8014     dummyStats.which = which;
8015     dummyStats.pv = "#";
8016     SetProgramStats( &dummyStats );
8017 }
8018
8019 #define MAXPLAYERS 500
8020
8021 char *
8022 TourneyStandings (int display)
8023 {
8024     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8025     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8026     char result, *p, *names[MAXPLAYERS];
8027
8028     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8029         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8030     names[0] = p = strdup(appData.participants);
8031     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8032
8033     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8034
8035     while(result = appData.results[nr]) {
8036         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8037         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8038         wScore = bScore = 0;
8039         switch(result) {
8040           case '+': wScore = 2; break;
8041           case '-': bScore = 2; break;
8042           case '=': wScore = bScore = 1; break;
8043           case ' ':
8044           case '*': return strdup("busy"); // tourney not finished
8045         }
8046         score[w] += wScore;
8047         score[b] += bScore;
8048         games[w]++;
8049         games[b]++;
8050         nr++;
8051     }
8052     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8053     for(w=0; w<nPlayers; w++) {
8054         bScore = -1;
8055         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8056         ranking[w] = b; points[w] = bScore; score[b] = -2;
8057     }
8058     p = malloc(nPlayers*34+1);
8059     for(w=0; w<nPlayers && w<display; w++)
8060         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8061     free(names[0]);
8062     return p;
8063 }
8064
8065 void
8066 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8067 {       // count all piece types
8068         int p, f, r;
8069         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8070         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8071         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8072                 p = board[r][f];
8073                 pCnt[p]++;
8074                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8075                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8076                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8077                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8078                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8079                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8080         }
8081 }
8082
8083 int
8084 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8085 {
8086         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8087         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8088
8089         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8090         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8091         if(myPawns == 2 && nMine == 3) // KPP
8092             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8093         if(myPawns == 1 && nMine == 2) // KP
8094             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8095         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8096             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8097         if(myPawns) return FALSE;
8098         if(pCnt[WhiteRook+side])
8099             return pCnt[BlackRook-side] ||
8100                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8101                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8102                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8103         if(pCnt[WhiteCannon+side]) {
8104             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8105             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8106         }
8107         if(pCnt[WhiteKnight+side])
8108             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8109         return FALSE;
8110 }
8111
8112 int
8113 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8114 {
8115         VariantClass v = gameInfo.variant;
8116
8117         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8118         if(v == VariantShatranj) return TRUE; // always winnable through baring
8119         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8120         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8121
8122         if(v == VariantXiangqi) {
8123                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8124
8125                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8126                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8127                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8128                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8129                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8130                 if(stale) // we have at least one last-rank P plus perhaps C
8131                     return majors // KPKX
8132                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8133                 else // KCA*E*
8134                     return pCnt[WhiteFerz+side] // KCAK
8135                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8136                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8137                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8138
8139         } else if(v == VariantKnightmate) {
8140                 if(nMine == 1) return FALSE;
8141                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8142         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8143                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8144
8145                 if(nMine == 1) return FALSE; // bare King
8146                 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
8147                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8148                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8149                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8150                 if(pCnt[WhiteKnight+side])
8151                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8152                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8153                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8154                 if(nBishops)
8155                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8156                 if(pCnt[WhiteAlfil+side])
8157                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8158                 if(pCnt[WhiteWazir+side])
8159                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8160         }
8161
8162         return TRUE;
8163 }
8164
8165 int
8166 CompareWithRights (Board b1, Board b2)
8167 {
8168     int rights = 0;
8169     if(!CompareBoards(b1, b2)) return FALSE;
8170     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8171     /* compare castling rights */
8172     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8173            rights++; /* King lost rights, while rook still had them */
8174     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8175         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8176            rights++; /* but at least one rook lost them */
8177     }
8178     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8179            rights++;
8180     if( b1[CASTLING][5] != NoRights ) {
8181         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8182            rights++;
8183     }
8184     return rights == 0;
8185 }
8186
8187 int
8188 Adjudicate (ChessProgramState *cps)
8189 {       // [HGM] some adjudications useful with buggy engines
8190         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8191         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8192         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8193         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8194         int k, drop, count = 0; static int bare = 1;
8195         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8196         Boolean canAdjudicate = !appData.icsActive;
8197
8198         // most tests only when we understand the game, i.e. legality-checking on
8199             if( appData.testLegality )
8200             {   /* [HGM] Some more adjudications for obstinate engines */
8201                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8202                 static int moveCount = 6;
8203                 ChessMove result;
8204                 char *reason = NULL;
8205
8206                 /* Count what is on board. */
8207                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8208
8209                 /* Some material-based adjudications that have to be made before stalemate test */
8210                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8211                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8212                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8213                      if(canAdjudicate && appData.checkMates) {
8214                          if(engineOpponent)
8215                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8216                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8217                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8218                          return 1;
8219                      }
8220                 }
8221
8222                 /* Bare King in Shatranj (loses) or Losers (wins) */
8223                 if( nrW == 1 || nrB == 1) {
8224                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8225                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8226                      if(canAdjudicate && appData.checkMates) {
8227                          if(engineOpponent)
8228                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8229                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8230                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8231                          return 1;
8232                      }
8233                   } else
8234                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8235                   {    /* bare King */
8236                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8237                         if(canAdjudicate && appData.checkMates) {
8238                             /* but only adjudicate if adjudication enabled */
8239                             if(engineOpponent)
8240                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8241                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8242                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8243                             return 1;
8244                         }
8245                   }
8246                 } else bare = 1;
8247
8248
8249             // don't wait for engine to announce game end if we can judge ourselves
8250             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8251               case MT_CHECK:
8252                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8253                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8254                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8255                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8256                             checkCnt++;
8257                         if(checkCnt >= 2) {
8258                             reason = "Xboard adjudication: 3rd check";
8259                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8260                             break;
8261                         }
8262                     }
8263                 }
8264               case MT_NONE:
8265               default:
8266                 break;
8267               case MT_STEALMATE:
8268               case MT_STALEMATE:
8269               case MT_STAINMATE:
8270                 reason = "Xboard adjudication: Stalemate";
8271                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8272                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8273                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8274                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8275                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8276                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8277                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8278                                                                         EP_CHECKMATE : EP_WINS);
8279                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8280                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8281                 }
8282                 break;
8283               case MT_CHECKMATE:
8284                 reason = "Xboard adjudication: Checkmate";
8285                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8286                 if(gameInfo.variant == VariantShogi) {
8287                     if(forwardMostMove > backwardMostMove
8288                        && moveList[forwardMostMove-1][1] == '@'
8289                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8290                         reason = "XBoard adjudication: pawn-drop mate";
8291                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8292                     }
8293                 }
8294                 break;
8295             }
8296
8297                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8298                     case EP_STALEMATE:
8299                         result = GameIsDrawn; break;
8300                     case EP_CHECKMATE:
8301                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8302                     case EP_WINS:
8303                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8304                     default:
8305                         result = EndOfFile;
8306                 }
8307                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8308                     if(engineOpponent)
8309                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8310                     GameEnds( result, reason, GE_XBOARD );
8311                     return 1;
8312                 }
8313
8314                 /* Next absolutely insufficient mating material. */
8315                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8316                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8317                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8318
8319                      /* always flag draws, for judging claims */
8320                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8321
8322                      if(canAdjudicate && appData.materialDraws) {
8323                          /* but only adjudicate them if adjudication enabled */
8324                          if(engineOpponent) {
8325                            SendToProgram("force\n", engineOpponent); // suppress reply
8326                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8327                          }
8328                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8329                          return 1;
8330                      }
8331                 }
8332
8333                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8334                 if(gameInfo.variant == VariantXiangqi ?
8335                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8336                  : nrW + nrB == 4 &&
8337                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8338                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8339                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8340                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8341                    ) ) {
8342                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8343                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8344                           if(engineOpponent) {
8345                             SendToProgram("force\n", engineOpponent); // suppress reply
8346                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8347                           }
8348                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8349                           return 1;
8350                      }
8351                 } else moveCount = 6;
8352             }
8353
8354         // Repetition draws and 50-move rule can be applied independently of legality testing
8355
8356                 /* Check for rep-draws */
8357                 count = 0;
8358                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8359                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8360                 for(k = forwardMostMove-2;
8361                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8362                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8363                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8364                     k-=2)
8365                 {   int rights=0;
8366                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8367                         /* compare castling rights */
8368                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8369                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8370                                 rights++; /* King lost rights, while rook still had them */
8371                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8372                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8373                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8374                                    rights++; /* but at least one rook lost them */
8375                         }
8376                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8377                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8378                                 rights++;
8379                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8380                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8381                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8382                                    rights++;
8383                         }
8384                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8385                             && appData.drawRepeats > 1) {
8386                              /* adjudicate after user-specified nr of repeats */
8387                              int result = GameIsDrawn;
8388                              char *details = "XBoard adjudication: repetition draw";
8389                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8390                                 // [HGM] xiangqi: check for forbidden perpetuals
8391                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8392                                 for(m=forwardMostMove; m>k; m-=2) {
8393                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8394                                         ourPerpetual = 0; // the current mover did not always check
8395                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8396                                         hisPerpetual = 0; // the opponent did not always check
8397                                 }
8398                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8399                                                                         ourPerpetual, hisPerpetual);
8400                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8401                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8402                                     details = "Xboard adjudication: perpetual checking";
8403                                 } else
8404                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8405                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8406                                 } else
8407                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8408                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8409                                         result = BlackWins;
8410                                         details = "Xboard adjudication: repetition";
8411                                     }
8412                                 } else // it must be XQ
8413                                 // Now check for perpetual chases
8414                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8415                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8416                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8417                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8418                                         static char resdet[MSG_SIZ];
8419                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8420                                         details = resdet;
8421                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8422                                     } else
8423                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8424                                         break; // Abort repetition-checking loop.
8425                                 }
8426                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8427                              }
8428                              if(engineOpponent) {
8429                                SendToProgram("force\n", engineOpponent); // suppress reply
8430                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8431                              }
8432                              GameEnds( result, details, GE_XBOARD );
8433                              return 1;
8434                         }
8435                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8436                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8437                     }
8438                 }
8439
8440                 /* Now we test for 50-move draws. Determine ply count */
8441                 count = forwardMostMove;
8442                 /* look for last irreversble move */
8443                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8444                     count--;
8445                 /* if we hit starting position, add initial plies */
8446                 if( count == backwardMostMove )
8447                     count -= initialRulePlies;
8448                 count = forwardMostMove - count;
8449                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8450                         // adjust reversible move counter for checks in Xiangqi
8451                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8452                         if(i < backwardMostMove) i = backwardMostMove;
8453                         while(i <= forwardMostMove) {
8454                                 lastCheck = inCheck; // check evasion does not count
8455                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8456                                 if(inCheck || lastCheck) count--; // check does not count
8457                                 i++;
8458                         }
8459                 }
8460                 if( count >= 100)
8461                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8462                          /* this is used to judge if draw claims are legal */
8463                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8464                          if(engineOpponent) {
8465                            SendToProgram("force\n", engineOpponent); // suppress reply
8466                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8467                          }
8468                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8469                          return 1;
8470                 }
8471
8472                 /* if draw offer is pending, treat it as a draw claim
8473                  * when draw condition present, to allow engines a way to
8474                  * claim draws before making their move to avoid a race
8475                  * condition occurring after their move
8476                  */
8477                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8478                          char *p = NULL;
8479                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8480                              p = "Draw claim: 50-move rule";
8481                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8482                              p = "Draw claim: 3-fold repetition";
8483                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8484                              p = "Draw claim: insufficient mating material";
8485                          if( p != NULL && canAdjudicate) {
8486                              if(engineOpponent) {
8487                                SendToProgram("force\n", engineOpponent); // suppress reply
8488                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8489                              }
8490                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8491                              return 1;
8492                          }
8493                 }
8494
8495                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8496                     if(engineOpponent) {
8497                       SendToProgram("force\n", engineOpponent); // suppress reply
8498                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8499                     }
8500                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8501                     return 1;
8502                 }
8503         return 0;
8504 }
8505
8506 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8507 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8508 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8509
8510 static int
8511 BitbaseProbe ()
8512 {
8513     int pieces[10], squares[10], cnt=0, r, f, res;
8514     static int loaded;
8515     static PPROBE_EGBB probeBB;
8516     if(!appData.testLegality) return 10;
8517     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8518     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8519     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8520     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8521         ChessSquare piece = boards[forwardMostMove][r][f];
8522         int black = (piece >= BlackPawn);
8523         int type = piece - black*BlackPawn;
8524         if(piece == EmptySquare) continue;
8525         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8526         if(type == WhiteKing) type = WhiteQueen + 1;
8527         type = egbbCode[type];
8528         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8529         pieces[cnt] = type + black*6;
8530         if(++cnt > 5) return 11;
8531     }
8532     pieces[cnt] = squares[cnt] = 0;
8533     // probe EGBB
8534     if(loaded == 2) return 13; // loading failed before
8535     if(loaded == 0) {
8536         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8537         HMODULE lib;
8538         PLOAD_EGBB loadBB;
8539         loaded = 2; // prepare for failure
8540         if(!path) return 13; // no egbb installed
8541         strncpy(buf, path + 8, MSG_SIZ);
8542         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8543         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8544         lib = LoadLibrary(buf);
8545         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8546         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8547         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8548         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8549         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8550         loaded = 1; // success!
8551     }
8552     res = probeBB(forwardMostMove & 1, pieces, squares);
8553     return res > 0 ? 1 : res < 0 ? -1 : 0;
8554 }
8555
8556 char *
8557 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8558 {   // [HGM] book: this routine intercepts moves to simulate book replies
8559     char *bookHit = NULL;
8560
8561     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8562         char buf[MSG_SIZ];
8563         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8564         SendToProgram(buf, cps);
8565     }
8566     //first determine if the incoming move brings opponent into his book
8567     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8568         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8569     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8570     if(bookHit != NULL && !cps->bookSuspend) {
8571         // make sure opponent is not going to reply after receiving move to book position
8572         SendToProgram("force\n", cps);
8573         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8574     }
8575     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8576     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8577     // now arrange restart after book miss
8578     if(bookHit) {
8579         // after a book hit we never send 'go', and the code after the call to this routine
8580         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8581         char buf[MSG_SIZ], *move = bookHit;
8582         if(cps->useSAN) {
8583             int fromX, fromY, toX, toY;
8584             char promoChar;
8585             ChessMove moveType;
8586             move = buf + 30;
8587             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8588                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8589                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8590                                     PosFlags(forwardMostMove),
8591                                     fromY, fromX, toY, toX, promoChar, move);
8592             } else {
8593                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8594                 bookHit = NULL;
8595             }
8596         }
8597         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8598         SendToProgram(buf, cps);
8599         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8600     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8601         SendToProgram("go\n", cps);
8602         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8603     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8604         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8605             SendToProgram("go\n", cps);
8606         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8607     }
8608     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8609 }
8610
8611 int
8612 LoadError (char *errmess, ChessProgramState *cps)
8613 {   // unloads engine and switches back to -ncp mode if it was first
8614     if(cps->initDone) return FALSE;
8615     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8616     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8617     cps->pr = NoProc;
8618     if(cps == &first) {
8619         appData.noChessProgram = TRUE;
8620         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8621         gameMode = BeginningOfGame; ModeHighlight();
8622         SetNCPMode();
8623     }
8624     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8625     DisplayMessage("", ""); // erase waiting message
8626     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8627     return TRUE;
8628 }
8629
8630 char *savedMessage;
8631 ChessProgramState *savedState;
8632 void
8633 DeferredBookMove (void)
8634 {
8635         if(savedState->lastPing != savedState->lastPong)
8636                     ScheduleDelayedEvent(DeferredBookMove, 10);
8637         else
8638         HandleMachineMove(savedMessage, savedState);
8639 }
8640
8641 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8642 static ChessProgramState *stalledEngine;
8643 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8644
8645 void
8646 HandleMachineMove (char *message, ChessProgramState *cps)
8647 {
8648     static char firstLeg[20];
8649     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8650     char realname[MSG_SIZ];
8651     int fromX, fromY, toX, toY;
8652     ChessMove moveType;
8653     char promoChar, roar;
8654     char *p, *pv=buf1;
8655     int oldError;
8656     char *bookHit;
8657
8658     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8659         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8660         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8661             DisplayError(_("Invalid pairing from pairing engine"), 0);
8662             return;
8663         }
8664         pairingReceived = 1;
8665         NextMatchGame();
8666         return; // Skim the pairing messages here.
8667     }
8668
8669     oldError = cps->userError; cps->userError = 0;
8670
8671 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8672     /*
8673      * Kludge to ignore BEL characters
8674      */
8675     while (*message == '\007') message++;
8676
8677     /*
8678      * [HGM] engine debug message: ignore lines starting with '#' character
8679      */
8680     if(cps->debug && *message == '#') return;
8681
8682     /*
8683      * Look for book output
8684      */
8685     if (cps == &first && bookRequested) {
8686         if (message[0] == '\t' || message[0] == ' ') {
8687             /* Part of the book output is here; append it */
8688             strcat(bookOutput, message);
8689             strcat(bookOutput, "  \n");
8690             return;
8691         } else if (bookOutput[0] != NULLCHAR) {
8692             /* All of book output has arrived; display it */
8693             char *p = bookOutput;
8694             while (*p != NULLCHAR) {
8695                 if (*p == '\t') *p = ' ';
8696                 p++;
8697             }
8698             DisplayInformation(bookOutput);
8699             bookRequested = FALSE;
8700             /* Fall through to parse the current output */
8701         }
8702     }
8703
8704     /*
8705      * Look for machine move.
8706      */
8707     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8708         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8709     {
8710         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8711             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8712             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8713             stalledEngine = cps;
8714             if(appData.ponderNextMove) { // bring opponent out of ponder
8715                 if(gameMode == TwoMachinesPlay) {
8716                     if(cps->other->pause)
8717                         PauseEngine(cps->other);
8718                     else
8719                         SendToProgram("easy\n", cps->other);
8720                 }
8721             }
8722             StopClocks();
8723             return;
8724         }
8725
8726       if(cps->usePing) {
8727
8728         /* This method is only useful on engines that support ping */
8729         if(abortEngineThink) {
8730             if (appData.debugMode) {
8731                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8732             }
8733             SendToProgram("undo\n", cps);
8734             return;
8735         }
8736
8737         if (cps->lastPing != cps->lastPong) {
8738             /* Extra move from before last new; ignore */
8739             if (appData.debugMode) {
8740                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8741             }
8742           return;
8743         }
8744
8745       } else {
8746
8747         int machineWhite = FALSE;
8748
8749         switch (gameMode) {
8750           case BeginningOfGame:
8751             /* Extra move from before last reset; ignore */
8752             if (appData.debugMode) {
8753                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8754             }
8755             return;
8756
8757           case EndOfGame:
8758           case IcsIdle:
8759           default:
8760             /* Extra move after we tried to stop.  The mode test is
8761                not a reliable way of detecting this problem, but it's
8762                the best we can do on engines that don't support ping.
8763             */
8764             if (appData.debugMode) {
8765                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8766                         cps->which, gameMode);
8767             }
8768             SendToProgram("undo\n", cps);
8769             return;
8770
8771           case MachinePlaysWhite:
8772           case IcsPlayingWhite:
8773             machineWhite = TRUE;
8774             break;
8775
8776           case MachinePlaysBlack:
8777           case IcsPlayingBlack:
8778             machineWhite = FALSE;
8779             break;
8780
8781           case TwoMachinesPlay:
8782             machineWhite = (cps->twoMachinesColor[0] == 'w');
8783             break;
8784         }
8785         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8786             if (appData.debugMode) {
8787                 fprintf(debugFP,
8788                         "Ignoring move out of turn by %s, gameMode %d"
8789                         ", forwardMost %d\n",
8790                         cps->which, gameMode, forwardMostMove);
8791             }
8792             return;
8793         }
8794       }
8795
8796         if(cps->alphaRank) AlphaRank(machineMove, 4);
8797
8798         // [HGM] lion: (some very limited) support for Alien protocol
8799         killX = killY = kill2X = kill2Y = -1;
8800         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8801             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8802             return;
8803         }
8804         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8805             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8806             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8807         }
8808         if(firstLeg[0]) { // there was a previous leg;
8809             // only support case where same piece makes two step
8810             char buf[20], *p = machineMove+1, *q = buf+1, f;
8811             safeStrCpy(buf, machineMove, 20);
8812             while(isdigit(*q)) q++; // find start of to-square
8813             safeStrCpy(machineMove, firstLeg, 20);
8814             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8815             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8816             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8817             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8818             firstLeg[0] = NULLCHAR;
8819         }
8820
8821         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8822                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8823             /* Machine move could not be parsed; ignore it. */
8824           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8825                     machineMove, _(cps->which));
8826             DisplayMoveError(buf1);
8827             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8828                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8829             if (gameMode == TwoMachinesPlay) {
8830               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8831                        buf1, GE_XBOARD);
8832             }
8833             return;
8834         }
8835
8836         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8837         /* So we have to redo legality test with true e.p. status here,  */
8838         /* to make sure an illegal e.p. capture does not slip through,   */
8839         /* to cause a forfeit on a justified illegal-move complaint      */
8840         /* of the opponent.                                              */
8841         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8842            ChessMove moveType;
8843            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8844                              fromY, fromX, toY, toX, promoChar);
8845             if(moveType == IllegalMove) {
8846               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8847                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8848                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8849                            buf1, GE_XBOARD);
8850                 return;
8851            } else if(!appData.fischerCastling)
8852            /* [HGM] Kludge to handle engines that send FRC-style castling
8853               when they shouldn't (like TSCP-Gothic) */
8854            switch(moveType) {
8855              case WhiteASideCastleFR:
8856              case BlackASideCastleFR:
8857                toX+=2;
8858                currentMoveString[2]++;
8859                break;
8860              case WhiteHSideCastleFR:
8861              case BlackHSideCastleFR:
8862                toX--;
8863                currentMoveString[2]--;
8864                break;
8865              default: ; // nothing to do, but suppresses warning of pedantic compilers
8866            }
8867         }
8868         hintRequested = FALSE;
8869         lastHint[0] = NULLCHAR;
8870         bookRequested = FALSE;
8871         /* Program may be pondering now */
8872         cps->maybeThinking = TRUE;
8873         if (cps->sendTime == 2) cps->sendTime = 1;
8874         if (cps->offeredDraw) cps->offeredDraw--;
8875
8876         /* [AS] Save move info*/
8877         pvInfoList[ forwardMostMove ].score = programStats.score;
8878         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8879         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8880
8881         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8882
8883         /* Test suites abort the 'game' after one move */
8884         if(*appData.finger) {
8885            static FILE *f;
8886            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8887            if(!f) f = fopen(appData.finger, "w");
8888            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8889            else { DisplayFatalError("Bad output file", errno, 0); return; }
8890            free(fen);
8891            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8892         }
8893         if(appData.epd) {
8894            if(solvingTime >= 0) {
8895               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8896               totalTime += solvingTime; first.matchWins++;
8897            } else {
8898               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8899               second.matchWins++;
8900            }
8901            OutputKibitz(2, buf1);
8902            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8903         }
8904
8905         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8906         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8907             int count = 0;
8908
8909             while( count < adjudicateLossPlies ) {
8910                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8911
8912                 if( count & 1 ) {
8913                     score = -score; /* Flip score for winning side */
8914                 }
8915
8916                 if( score > appData.adjudicateLossThreshold ) {
8917                     break;
8918                 }
8919
8920                 count++;
8921             }
8922
8923             if( count >= adjudicateLossPlies ) {
8924                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8925
8926                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8927                     "Xboard adjudication",
8928                     GE_XBOARD );
8929
8930                 return;
8931             }
8932         }
8933
8934         if(Adjudicate(cps)) {
8935             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8936             return; // [HGM] adjudicate: for all automatic game ends
8937         }
8938
8939 #if ZIPPY
8940         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8941             first.initDone) {
8942           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8943                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8944                 SendToICS("draw ");
8945                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8946           }
8947           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8948           ics_user_moved = 1;
8949           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8950                 char buf[3*MSG_SIZ];
8951
8952                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8953                         programStats.score / 100.,
8954                         programStats.depth,
8955                         programStats.time / 100.,
8956                         (unsigned int)programStats.nodes,
8957                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8958                         programStats.movelist);
8959                 SendToICS(buf);
8960           }
8961         }
8962 #endif
8963
8964         /* [AS] Clear stats for next move */
8965         ClearProgramStats();
8966         thinkOutput[0] = NULLCHAR;
8967         hiddenThinkOutputState = 0;
8968
8969         bookHit = NULL;
8970         if (gameMode == TwoMachinesPlay) {
8971             /* [HGM] relaying draw offers moved to after reception of move */
8972             /* and interpreting offer as claim if it brings draw condition */
8973             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8974                 SendToProgram("draw\n", cps->other);
8975             }
8976             if (cps->other->sendTime) {
8977                 SendTimeRemaining(cps->other,
8978                                   cps->other->twoMachinesColor[0] == 'w');
8979             }
8980             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8981             if (firstMove && !bookHit) {
8982                 firstMove = FALSE;
8983                 if (cps->other->useColors) {
8984                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8985                 }
8986                 SendToProgram("go\n", cps->other);
8987             }
8988             cps->other->maybeThinking = TRUE;
8989         }
8990
8991         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8992
8993         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8994
8995         if (!pausing && appData.ringBellAfterMoves) {
8996             if(!roar) RingBell();
8997         }
8998
8999         /*
9000          * Reenable menu items that were disabled while
9001          * machine was thinking
9002          */
9003         if (gameMode != TwoMachinesPlay)
9004             SetUserThinkingEnables();
9005
9006         // [HGM] book: after book hit opponent has received move and is now in force mode
9007         // force the book reply into it, and then fake that it outputted this move by jumping
9008         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9009         if(bookHit) {
9010                 static char bookMove[MSG_SIZ]; // a bit generous?
9011
9012                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9013                 strcat(bookMove, bookHit);
9014                 message = bookMove;
9015                 cps = cps->other;
9016                 programStats.nodes = programStats.depth = programStats.time =
9017                 programStats.score = programStats.got_only_move = 0;
9018                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9019
9020                 if(cps->lastPing != cps->lastPong) {
9021                     savedMessage = message; // args for deferred call
9022                     savedState = cps;
9023                     ScheduleDelayedEvent(DeferredBookMove, 10);
9024                     return;
9025                 }
9026                 goto FakeBookMove;
9027         }
9028
9029         return;
9030     }
9031
9032     /* Set special modes for chess engines.  Later something general
9033      *  could be added here; for now there is just one kludge feature,
9034      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9035      *  when "xboard" is given as an interactive command.
9036      */
9037     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9038         cps->useSigint = FALSE;
9039         cps->useSigterm = FALSE;
9040     }
9041     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9042       ParseFeatures(message+8, cps);
9043       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9044     }
9045
9046     if (!strncmp(message, "setup ", 6) && 
9047         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9048           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9049                                         ) { // [HGM] allow first engine to define opening position
9050       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9051       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9052       *buf = NULLCHAR;
9053       if(sscanf(message, "setup (%s", buf) == 1) {
9054         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9055         ASSIGN(appData.pieceToCharTable, buf);
9056       }
9057       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9058       if(dummy >= 3) {
9059         while(message[s] && message[s++] != ' ');
9060         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9061            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9062             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9063             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9064           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9065           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9066           startedFromSetupPosition = FALSE;
9067         }
9068       }
9069       if(startedFromSetupPosition) return;
9070       ParseFEN(boards[0], &dummy, message+s, FALSE);
9071       DrawPosition(TRUE, boards[0]);
9072       CopyBoard(initialPosition, boards[0]);
9073       startedFromSetupPosition = TRUE;
9074       return;
9075     }
9076     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9077       ChessSquare piece = WhitePawn;
9078       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9079       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9080       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9081       piece += CharToPiece(ID & 255) - WhitePawn;
9082       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9083       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9084       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9085       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9086       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9087       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9088                                                && gameInfo.variant != VariantGreat
9089                                                && gameInfo.variant != VariantFairy    ) return;
9090       if(piece < EmptySquare) {
9091         pieceDefs = TRUE;
9092         ASSIGN(pieceDesc[piece], buf1);
9093         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9094       }
9095       return;
9096     }
9097     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9098       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9099       Sweep(0);
9100       return;
9101     }
9102     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9103      * want this, I was asked to put it in, and obliged.
9104      */
9105     if (!strncmp(message, "setboard ", 9)) {
9106         Board initial_position;
9107
9108         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9109
9110         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9111             DisplayError(_("Bad FEN received from engine"), 0);
9112             return ;
9113         } else {
9114            Reset(TRUE, FALSE);
9115            CopyBoard(boards[0], initial_position);
9116            initialRulePlies = FENrulePlies;
9117            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9118            else gameMode = MachinePlaysBlack;
9119            DrawPosition(FALSE, boards[currentMove]);
9120         }
9121         return;
9122     }
9123
9124     /*
9125      * Look for communication commands
9126      */
9127     if (!strncmp(message, "telluser ", 9)) {
9128         if(message[9] == '\\' && message[10] == '\\')
9129             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9130         PlayTellSound();
9131         DisplayNote(message + 9);
9132         return;
9133     }
9134     if (!strncmp(message, "tellusererror ", 14)) {
9135         cps->userError = 1;
9136         if(message[14] == '\\' && message[15] == '\\')
9137             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9138         PlayTellSound();
9139         DisplayError(message + 14, 0);
9140         return;
9141     }
9142     if (!strncmp(message, "tellopponent ", 13)) {
9143       if (appData.icsActive) {
9144         if (loggedOn) {
9145           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9146           SendToICS(buf1);
9147         }
9148       } else {
9149         DisplayNote(message + 13);
9150       }
9151       return;
9152     }
9153     if (!strncmp(message, "tellothers ", 11)) {
9154       if (appData.icsActive) {
9155         if (loggedOn) {
9156           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9157           SendToICS(buf1);
9158         }
9159       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9160       return;
9161     }
9162     if (!strncmp(message, "tellall ", 8)) {
9163       if (appData.icsActive) {
9164         if (loggedOn) {
9165           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9166           SendToICS(buf1);
9167         }
9168       } else {
9169         DisplayNote(message + 8);
9170       }
9171       return;
9172     }
9173     if (strncmp(message, "warning", 7) == 0) {
9174         /* Undocumented feature, use tellusererror in new code */
9175         DisplayError(message, 0);
9176         return;
9177     }
9178     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9179         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9180         strcat(realname, " query");
9181         AskQuestion(realname, buf2, buf1, cps->pr);
9182         return;
9183     }
9184     /* Commands from the engine directly to ICS.  We don't allow these to be
9185      *  sent until we are logged on. Crafty kibitzes have been known to
9186      *  interfere with the login process.
9187      */
9188     if (loggedOn) {
9189         if (!strncmp(message, "tellics ", 8)) {
9190             SendToICS(message + 8);
9191             SendToICS("\n");
9192             return;
9193         }
9194         if (!strncmp(message, "tellicsnoalias ", 15)) {
9195             SendToICS(ics_prefix);
9196             SendToICS(message + 15);
9197             SendToICS("\n");
9198             return;
9199         }
9200         /* The following are for backward compatibility only */
9201         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9202             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9203             SendToICS(ics_prefix);
9204             SendToICS(message);
9205             SendToICS("\n");
9206             return;
9207         }
9208     }
9209     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9210         if(initPing == cps->lastPong) {
9211             if(gameInfo.variant == VariantUnknown) {
9212                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9213                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9214                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9215             }
9216             initPing = -1;
9217         }
9218         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9219             abortEngineThink = FALSE;
9220             DisplayMessage("", "");
9221             ThawUI();
9222         }
9223         return;
9224     }
9225     if(!strncmp(message, "highlight ", 10)) {
9226         if(appData.testLegality && !*engineVariant && appData.markers) return;
9227         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9228         return;
9229     }
9230     if(!strncmp(message, "click ", 6)) {
9231         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9232         if(appData.testLegality || !appData.oneClick) return;
9233         sscanf(message+6, "%c%d%c", &f, &y, &c);
9234         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9235         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9236         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9237         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9238         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9239         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9240             LeftClick(Release, lastLeftX, lastLeftY);
9241         controlKey  = (c == ',');
9242         LeftClick(Press, x, y);
9243         LeftClick(Release, x, y);
9244         first.highlight = f;
9245         return;
9246     }
9247     /*
9248      * If the move is illegal, cancel it and redraw the board.
9249      * Also deal with other error cases.  Matching is rather loose
9250      * here to accommodate engines written before the spec.
9251      */
9252     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9253         strncmp(message, "Error", 5) == 0) {
9254         if (StrStr(message, "name") ||
9255             StrStr(message, "rating") || StrStr(message, "?") ||
9256             StrStr(message, "result") || StrStr(message, "board") ||
9257             StrStr(message, "bk") || StrStr(message, "computer") ||
9258             StrStr(message, "variant") || StrStr(message, "hint") ||
9259             StrStr(message, "random") || StrStr(message, "depth") ||
9260             StrStr(message, "accepted")) {
9261             return;
9262         }
9263         if (StrStr(message, "protover")) {
9264           /* Program is responding to input, so it's apparently done
9265              initializing, and this error message indicates it is
9266              protocol version 1.  So we don't need to wait any longer
9267              for it to initialize and send feature commands. */
9268           FeatureDone(cps, 1);
9269           cps->protocolVersion = 1;
9270           return;
9271         }
9272         cps->maybeThinking = FALSE;
9273
9274         if (StrStr(message, "draw")) {
9275             /* Program doesn't have "draw" command */
9276             cps->sendDrawOffers = 0;
9277             return;
9278         }
9279         if (cps->sendTime != 1 &&
9280             (StrStr(message, "time") || StrStr(message, "otim"))) {
9281           /* Program apparently doesn't have "time" or "otim" command */
9282           cps->sendTime = 0;
9283           return;
9284         }
9285         if (StrStr(message, "analyze")) {
9286             cps->analysisSupport = FALSE;
9287             cps->analyzing = FALSE;
9288 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9289             EditGameEvent(); // [HGM] try to preserve loaded game
9290             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9291             DisplayError(buf2, 0);
9292             return;
9293         }
9294         if (StrStr(message, "(no matching move)st")) {
9295           /* Special kludge for GNU Chess 4 only */
9296           cps->stKludge = TRUE;
9297           SendTimeControl(cps, movesPerSession, timeControl,
9298                           timeIncrement, appData.searchDepth,
9299                           searchTime);
9300           return;
9301         }
9302         if (StrStr(message, "(no matching move)sd")) {
9303           /* Special kludge for GNU Chess 4 only */
9304           cps->sdKludge = TRUE;
9305           SendTimeControl(cps, movesPerSession, timeControl,
9306                           timeIncrement, appData.searchDepth,
9307                           searchTime);
9308           return;
9309         }
9310         if (!StrStr(message, "llegal")) {
9311             return;
9312         }
9313         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9314             gameMode == IcsIdle) return;
9315         if (forwardMostMove <= backwardMostMove) return;
9316         if (pausing) PauseEvent();
9317       if(appData.forceIllegal) {
9318             // [HGM] illegal: machine refused move; force position after move into it
9319           SendToProgram("force\n", cps);
9320           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9321                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9322                 // when black is to move, while there might be nothing on a2 or black
9323                 // might already have the move. So send the board as if white has the move.
9324                 // But first we must change the stm of the engine, as it refused the last move
9325                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9326                 if(WhiteOnMove(forwardMostMove)) {
9327                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9328                     SendBoard(cps, forwardMostMove); // kludgeless board
9329                 } else {
9330                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9331                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9332                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9333                 }
9334           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9335             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9336                  gameMode == TwoMachinesPlay)
9337               SendToProgram("go\n", cps);
9338             return;
9339       } else
9340         if (gameMode == PlayFromGameFile) {
9341             /* Stop reading this game file */
9342             gameMode = EditGame;
9343             ModeHighlight();
9344         }
9345         /* [HGM] illegal-move claim should forfeit game when Xboard */
9346         /* only passes fully legal moves                            */
9347         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9348             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9349                                 "False illegal-move claim", GE_XBOARD );
9350             return; // do not take back move we tested as valid
9351         }
9352         currentMove = forwardMostMove-1;
9353         DisplayMove(currentMove-1); /* before DisplayMoveError */
9354         SwitchClocks(forwardMostMove-1); // [HGM] race
9355         DisplayBothClocks();
9356         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9357                 parseList[currentMove], _(cps->which));
9358         DisplayMoveError(buf1);
9359         DrawPosition(FALSE, boards[currentMove]);
9360
9361         SetUserThinkingEnables();
9362         return;
9363     }
9364     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9365         /* Program has a broken "time" command that
9366            outputs a string not ending in newline.
9367            Don't use it. */
9368         cps->sendTime = 0;
9369     }
9370     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9371         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9372             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9373     }
9374
9375     /*
9376      * If chess program startup fails, exit with an error message.
9377      * Attempts to recover here are futile. [HGM] Well, we try anyway
9378      */
9379     if ((StrStr(message, "unknown host") != NULL)
9380         || (StrStr(message, "No remote directory") != NULL)
9381         || (StrStr(message, "not found") != NULL)
9382         || (StrStr(message, "No such file") != NULL)
9383         || (StrStr(message, "can't alloc") != NULL)
9384         || (StrStr(message, "Permission denied") != NULL)) {
9385
9386         cps->maybeThinking = FALSE;
9387         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9388                 _(cps->which), cps->program, cps->host, message);
9389         RemoveInputSource(cps->isr);
9390         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9391             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9392             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9393         }
9394         return;
9395     }
9396
9397     /*
9398      * Look for hint output
9399      */
9400     if (sscanf(message, "Hint: %s", buf1) == 1) {
9401         if (cps == &first && hintRequested) {
9402             hintRequested = FALSE;
9403             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9404                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9405                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9406                                     PosFlags(forwardMostMove),
9407                                     fromY, fromX, toY, toX, promoChar, buf1);
9408                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9409                 DisplayInformation(buf2);
9410             } else {
9411                 /* Hint move could not be parsed!? */
9412               snprintf(buf2, sizeof(buf2),
9413                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9414                         buf1, _(cps->which));
9415                 DisplayError(buf2, 0);
9416             }
9417         } else {
9418           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9419         }
9420         return;
9421     }
9422
9423     /*
9424      * Ignore other messages if game is not in progress
9425      */
9426     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9427         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9428
9429     /*
9430      * look for win, lose, draw, or draw offer
9431      */
9432     if (strncmp(message, "1-0", 3) == 0) {
9433         char *p, *q, *r = "";
9434         p = strchr(message, '{');
9435         if (p) {
9436             q = strchr(p, '}');
9437             if (q) {
9438                 *q = NULLCHAR;
9439                 r = p + 1;
9440             }
9441         }
9442         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9443         return;
9444     } else if (strncmp(message, "0-1", 3) == 0) {
9445         char *p, *q, *r = "";
9446         p = strchr(message, '{');
9447         if (p) {
9448             q = strchr(p, '}');
9449             if (q) {
9450                 *q = NULLCHAR;
9451                 r = p + 1;
9452             }
9453         }
9454         /* Kludge for Arasan 4.1 bug */
9455         if (strcmp(r, "Black resigns") == 0) {
9456             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9457             return;
9458         }
9459         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9460         return;
9461     } else if (strncmp(message, "1/2", 3) == 0) {
9462         char *p, *q, *r = "";
9463         p = strchr(message, '{');
9464         if (p) {
9465             q = strchr(p, '}');
9466             if (q) {
9467                 *q = NULLCHAR;
9468                 r = p + 1;
9469             }
9470         }
9471
9472         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9473         return;
9474
9475     } else if (strncmp(message, "White resign", 12) == 0) {
9476         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9477         return;
9478     } else if (strncmp(message, "Black resign", 12) == 0) {
9479         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9480         return;
9481     } else if (strncmp(message, "White matches", 13) == 0 ||
9482                strncmp(message, "Black matches", 13) == 0   ) {
9483         /* [HGM] ignore GNUShogi noises */
9484         return;
9485     } else if (strncmp(message, "White", 5) == 0 &&
9486                message[5] != '(' &&
9487                StrStr(message, "Black") == NULL) {
9488         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9489         return;
9490     } else if (strncmp(message, "Black", 5) == 0 &&
9491                message[5] != '(') {
9492         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9493         return;
9494     } else if (strcmp(message, "resign") == 0 ||
9495                strcmp(message, "computer resigns") == 0) {
9496         switch (gameMode) {
9497           case MachinePlaysBlack:
9498           case IcsPlayingBlack:
9499             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9500             break;
9501           case MachinePlaysWhite:
9502           case IcsPlayingWhite:
9503             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9504             break;
9505           case TwoMachinesPlay:
9506             if (cps->twoMachinesColor[0] == 'w')
9507               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9508             else
9509               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9510             break;
9511           default:
9512             /* can't happen */
9513             break;
9514         }
9515         return;
9516     } else if (strncmp(message, "opponent mates", 14) == 0) {
9517         switch (gameMode) {
9518           case MachinePlaysBlack:
9519           case IcsPlayingBlack:
9520             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9521             break;
9522           case MachinePlaysWhite:
9523           case IcsPlayingWhite:
9524             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9525             break;
9526           case TwoMachinesPlay:
9527             if (cps->twoMachinesColor[0] == 'w')
9528               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9529             else
9530               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9531             break;
9532           default:
9533             /* can't happen */
9534             break;
9535         }
9536         return;
9537     } else if (strncmp(message, "computer mates", 14) == 0) {
9538         switch (gameMode) {
9539           case MachinePlaysBlack:
9540           case IcsPlayingBlack:
9541             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9542             break;
9543           case MachinePlaysWhite:
9544           case IcsPlayingWhite:
9545             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9546             break;
9547           case TwoMachinesPlay:
9548             if (cps->twoMachinesColor[0] == 'w')
9549               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9550             else
9551               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9552             break;
9553           default:
9554             /* can't happen */
9555             break;
9556         }
9557         return;
9558     } else if (strncmp(message, "checkmate", 9) == 0) {
9559         if (WhiteOnMove(forwardMostMove)) {
9560             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9561         } else {
9562             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9563         }
9564         return;
9565     } else if (strstr(message, "Draw") != NULL ||
9566                strstr(message, "game is a draw") != NULL) {
9567         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9568         return;
9569     } else if (strstr(message, "offer") != NULL &&
9570                strstr(message, "draw") != NULL) {
9571 #if ZIPPY
9572         if (appData.zippyPlay && first.initDone) {
9573             /* Relay offer to ICS */
9574             SendToICS(ics_prefix);
9575             SendToICS("draw\n");
9576         }
9577 #endif
9578         cps->offeredDraw = 2; /* valid until this engine moves twice */
9579         if (gameMode == TwoMachinesPlay) {
9580             if (cps->other->offeredDraw) {
9581                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9582             /* [HGM] in two-machine mode we delay relaying draw offer      */
9583             /* until after we also have move, to see if it is really claim */
9584             }
9585         } else if (gameMode == MachinePlaysWhite ||
9586                    gameMode == MachinePlaysBlack) {
9587           if (userOfferedDraw) {
9588             DisplayInformation(_("Machine accepts your draw offer"));
9589             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9590           } else {
9591             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9592           }
9593         }
9594     }
9595
9596
9597     /*
9598      * Look for thinking output
9599      */
9600     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9601           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9602                                 ) {
9603         int plylev, mvleft, mvtot, curscore, time;
9604         char mvname[MOVE_LEN];
9605         u64 nodes; // [DM]
9606         char plyext;
9607         int ignore = FALSE;
9608         int prefixHint = FALSE;
9609         mvname[0] = NULLCHAR;
9610
9611         switch (gameMode) {
9612           case MachinePlaysBlack:
9613           case IcsPlayingBlack:
9614             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9615             break;
9616           case MachinePlaysWhite:
9617           case IcsPlayingWhite:
9618             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9619             break;
9620           case AnalyzeMode:
9621           case AnalyzeFile:
9622             break;
9623           case IcsObserving: /* [DM] icsEngineAnalyze */
9624             if (!appData.icsEngineAnalyze) ignore = TRUE;
9625             break;
9626           case TwoMachinesPlay:
9627             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9628                 ignore = TRUE;
9629             }
9630             break;
9631           default:
9632             ignore = TRUE;
9633             break;
9634         }
9635
9636         if (!ignore) {
9637             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9638             buf1[0] = NULLCHAR;
9639             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9640                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9641                 char score_buf[MSG_SIZ];
9642
9643                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9644                     nodes += u64Const(0x100000000);
9645
9646                 if (plyext != ' ' && plyext != '\t') {
9647                     time *= 100;
9648                 }
9649
9650                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9651                 if( cps->scoreIsAbsolute &&
9652                     ( gameMode == MachinePlaysBlack ||
9653                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9654                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9655                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9656                      !WhiteOnMove(currentMove)
9657                     ) )
9658                 {
9659                     curscore = -curscore;
9660                 }
9661
9662                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9663
9664                 if(*bestMove) { // rememer time best EPD move was first found
9665                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9666                     ChessMove mt;
9667                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9668                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9669                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9670                 }
9671
9672                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9673                         char buf[MSG_SIZ];
9674                         FILE *f;
9675                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9676                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9677                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9678                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9679                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9680                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9681                                 fclose(f);
9682                         }
9683                         else
9684                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9685                           DisplayError(_("failed writing PV"), 0);
9686                 }
9687
9688                 tempStats.depth = plylev;
9689                 tempStats.nodes = nodes;
9690                 tempStats.time = time;
9691                 tempStats.score = curscore;
9692                 tempStats.got_only_move = 0;
9693
9694                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9695                         int ticklen;
9696
9697                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9698                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9699                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9700                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9701                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9702                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9703                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9704                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9705                 }
9706
9707                 /* Buffer overflow protection */
9708                 if (pv[0] != NULLCHAR) {
9709                     if (strlen(pv) >= sizeof(tempStats.movelist)
9710                         && appData.debugMode) {
9711                         fprintf(debugFP,
9712                                 "PV is too long; using the first %u bytes.\n",
9713                                 (unsigned) sizeof(tempStats.movelist) - 1);
9714                     }
9715
9716                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9717                 } else {
9718                     sprintf(tempStats.movelist, " no PV\n");
9719                 }
9720
9721                 if (tempStats.seen_stat) {
9722                     tempStats.ok_to_send = 1;
9723                 }
9724
9725                 if (strchr(tempStats.movelist, '(') != NULL) {
9726                     tempStats.line_is_book = 1;
9727                     tempStats.nr_moves = 0;
9728                     tempStats.moves_left = 0;
9729                 } else {
9730                     tempStats.line_is_book = 0;
9731                 }
9732
9733                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9734                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9735
9736                 SendProgramStatsToFrontend( cps, &tempStats );
9737
9738                 /*
9739                     [AS] Protect the thinkOutput buffer from overflow... this
9740                     is only useful if buf1 hasn't overflowed first!
9741                 */
9742                 if(curscore >= MATE_SCORE) 
9743                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9744                 else if(curscore <= -MATE_SCORE) 
9745                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9746                 else
9747                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9748                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9749                          plylev,
9750                          (gameMode == TwoMachinesPlay ?
9751                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9752                          score_buf,
9753                          prefixHint ? lastHint : "",
9754                          prefixHint ? " " : "" );
9755
9756                 if( buf1[0] != NULLCHAR ) {
9757                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9758
9759                     if( strlen(pv) > max_len ) {
9760                         if( appData.debugMode) {
9761                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9762                         }
9763                         pv[max_len+1] = '\0';
9764                     }
9765
9766                     strcat( thinkOutput, pv);
9767                 }
9768
9769                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9770                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9771                     DisplayMove(currentMove - 1);
9772                 }
9773                 return;
9774
9775             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9776                 /* crafty (9.25+) says "(only move) <move>"
9777                  * if there is only 1 legal move
9778                  */
9779                 sscanf(p, "(only move) %s", buf1);
9780                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9781                 sprintf(programStats.movelist, "%s (only move)", buf1);
9782                 programStats.depth = 1;
9783                 programStats.nr_moves = 1;
9784                 programStats.moves_left = 1;
9785                 programStats.nodes = 1;
9786                 programStats.time = 1;
9787                 programStats.got_only_move = 1;
9788
9789                 /* Not really, but we also use this member to
9790                    mean "line isn't going to change" (Crafty
9791                    isn't searching, so stats won't change) */
9792                 programStats.line_is_book = 1;
9793
9794                 SendProgramStatsToFrontend( cps, &programStats );
9795
9796                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9797                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9798                     DisplayMove(currentMove - 1);
9799                 }
9800                 return;
9801             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9802                               &time, &nodes, &plylev, &mvleft,
9803                               &mvtot, mvname) >= 5) {
9804                 /* The stat01: line is from Crafty (9.29+) in response
9805                    to the "." command */
9806                 programStats.seen_stat = 1;
9807                 cps->maybeThinking = TRUE;
9808
9809                 if (programStats.got_only_move || !appData.periodicUpdates)
9810                   return;
9811
9812                 programStats.depth = plylev;
9813                 programStats.time = time;
9814                 programStats.nodes = nodes;
9815                 programStats.moves_left = mvleft;
9816                 programStats.nr_moves = mvtot;
9817                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9818                 programStats.ok_to_send = 1;
9819                 programStats.movelist[0] = '\0';
9820
9821                 SendProgramStatsToFrontend( cps, &programStats );
9822
9823                 return;
9824
9825             } else if (strncmp(message,"++",2) == 0) {
9826                 /* Crafty 9.29+ outputs this */
9827                 programStats.got_fail = 2;
9828                 return;
9829
9830             } else if (strncmp(message,"--",2) == 0) {
9831                 /* Crafty 9.29+ outputs this */
9832                 programStats.got_fail = 1;
9833                 return;
9834
9835             } else if (thinkOutput[0] != NULLCHAR &&
9836                        strncmp(message, "    ", 4) == 0) {
9837                 unsigned message_len;
9838
9839                 p = message;
9840                 while (*p && *p == ' ') p++;
9841
9842                 message_len = strlen( p );
9843
9844                 /* [AS] Avoid buffer overflow */
9845                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9846                     strcat(thinkOutput, " ");
9847                     strcat(thinkOutput, p);
9848                 }
9849
9850                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9851                     strcat(programStats.movelist, " ");
9852                     strcat(programStats.movelist, p);
9853                 }
9854
9855                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9856                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9857                     DisplayMove(currentMove - 1);
9858                 }
9859                 return;
9860             }
9861         }
9862         else {
9863             buf1[0] = NULLCHAR;
9864
9865             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9866                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9867             {
9868                 ChessProgramStats cpstats;
9869
9870                 if (plyext != ' ' && plyext != '\t') {
9871                     time *= 100;
9872                 }
9873
9874                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9875                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9876                     curscore = -curscore;
9877                 }
9878
9879                 cpstats.depth = plylev;
9880                 cpstats.nodes = nodes;
9881                 cpstats.time = time;
9882                 cpstats.score = curscore;
9883                 cpstats.got_only_move = 0;
9884                 cpstats.movelist[0] = '\0';
9885
9886                 if (buf1[0] != NULLCHAR) {
9887                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9888                 }
9889
9890                 cpstats.ok_to_send = 0;
9891                 cpstats.line_is_book = 0;
9892                 cpstats.nr_moves = 0;
9893                 cpstats.moves_left = 0;
9894
9895                 SendProgramStatsToFrontend( cps, &cpstats );
9896             }
9897         }
9898     }
9899 }
9900
9901
9902 /* Parse a game score from the character string "game", and
9903    record it as the history of the current game.  The game
9904    score is NOT assumed to start from the standard position.
9905    The display is not updated in any way.
9906    */
9907 void
9908 ParseGameHistory (char *game)
9909 {
9910     ChessMove moveType;
9911     int fromX, fromY, toX, toY, boardIndex;
9912     char promoChar;
9913     char *p, *q;
9914     char buf[MSG_SIZ];
9915
9916     if (appData.debugMode)
9917       fprintf(debugFP, "Parsing game history: %s\n", game);
9918
9919     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9920     gameInfo.site = StrSave(appData.icsHost);
9921     gameInfo.date = PGNDate();
9922     gameInfo.round = StrSave("-");
9923
9924     /* Parse out names of players */
9925     while (*game == ' ') game++;
9926     p = buf;
9927     while (*game != ' ') *p++ = *game++;
9928     *p = NULLCHAR;
9929     gameInfo.white = StrSave(buf);
9930     while (*game == ' ') game++;
9931     p = buf;
9932     while (*game != ' ' && *game != '\n') *p++ = *game++;
9933     *p = NULLCHAR;
9934     gameInfo.black = StrSave(buf);
9935
9936     /* Parse moves */
9937     boardIndex = blackPlaysFirst ? 1 : 0;
9938     yynewstr(game);
9939     for (;;) {
9940         yyboardindex = boardIndex;
9941         moveType = (ChessMove) Myylex();
9942         switch (moveType) {
9943           case IllegalMove:             /* maybe suicide chess, etc. */
9944   if (appData.debugMode) {
9945     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9946     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9947     setbuf(debugFP, NULL);
9948   }
9949           case WhitePromotion:
9950           case BlackPromotion:
9951           case WhiteNonPromotion:
9952           case BlackNonPromotion:
9953           case NormalMove:
9954           case FirstLeg:
9955           case WhiteCapturesEnPassant:
9956           case BlackCapturesEnPassant:
9957           case WhiteKingSideCastle:
9958           case WhiteQueenSideCastle:
9959           case BlackKingSideCastle:
9960           case BlackQueenSideCastle:
9961           case WhiteKingSideCastleWild:
9962           case WhiteQueenSideCastleWild:
9963           case BlackKingSideCastleWild:
9964           case BlackQueenSideCastleWild:
9965           /* PUSH Fabien */
9966           case WhiteHSideCastleFR:
9967           case WhiteASideCastleFR:
9968           case BlackHSideCastleFR:
9969           case BlackASideCastleFR:
9970           /* POP Fabien */
9971             fromX = currentMoveString[0] - AAA;
9972             fromY = currentMoveString[1] - ONE;
9973             toX = currentMoveString[2] - AAA;
9974             toY = currentMoveString[3] - ONE;
9975             promoChar = currentMoveString[4];
9976             break;
9977           case WhiteDrop:
9978           case BlackDrop:
9979             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9980             fromX = moveType == WhiteDrop ?
9981               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9982             (int) CharToPiece(ToLower(currentMoveString[0]));
9983             fromY = DROP_RANK;
9984             toX = currentMoveString[2] - AAA;
9985             toY = currentMoveString[3] - ONE;
9986             promoChar = NULLCHAR;
9987             break;
9988           case AmbiguousMove:
9989             /* bug? */
9990             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9991   if (appData.debugMode) {
9992     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9993     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9994     setbuf(debugFP, NULL);
9995   }
9996             DisplayError(buf, 0);
9997             return;
9998           case ImpossibleMove:
9999             /* bug? */
10000             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10001   if (appData.debugMode) {
10002     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10003     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10004     setbuf(debugFP, NULL);
10005   }
10006             DisplayError(buf, 0);
10007             return;
10008           case EndOfFile:
10009             if (boardIndex < backwardMostMove) {
10010                 /* Oops, gap.  How did that happen? */
10011                 DisplayError(_("Gap in move list"), 0);
10012                 return;
10013             }
10014             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10015             if (boardIndex > forwardMostMove) {
10016                 forwardMostMove = boardIndex;
10017             }
10018             return;
10019           case ElapsedTime:
10020             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10021                 strcat(parseList[boardIndex-1], " ");
10022                 strcat(parseList[boardIndex-1], yy_text);
10023             }
10024             continue;
10025           case Comment:
10026           case PGNTag:
10027           case NAG:
10028           default:
10029             /* ignore */
10030             continue;
10031           case WhiteWins:
10032           case BlackWins:
10033           case GameIsDrawn:
10034           case GameUnfinished:
10035             if (gameMode == IcsExamining) {
10036                 if (boardIndex < backwardMostMove) {
10037                     /* Oops, gap.  How did that happen? */
10038                     return;
10039                 }
10040                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10041                 return;
10042             }
10043             gameInfo.result = moveType;
10044             p = strchr(yy_text, '{');
10045             if (p == NULL) p = strchr(yy_text, '(');
10046             if (p == NULL) {
10047                 p = yy_text;
10048                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10049             } else {
10050                 q = strchr(p, *p == '{' ? '}' : ')');
10051                 if (q != NULL) *q = NULLCHAR;
10052                 p++;
10053             }
10054             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10055             gameInfo.resultDetails = StrSave(p);
10056             continue;
10057         }
10058         if (boardIndex >= forwardMostMove &&
10059             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10060             backwardMostMove = blackPlaysFirst ? 1 : 0;
10061             return;
10062         }
10063         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10064                                  fromY, fromX, toY, toX, promoChar,
10065                                  parseList[boardIndex]);
10066         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10067         /* currentMoveString is set as a side-effect of yylex */
10068         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10069         strcat(moveList[boardIndex], "\n");
10070         boardIndex++;
10071         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10072         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10073           case MT_NONE:
10074           case MT_STALEMATE:
10075           default:
10076             break;
10077           case MT_CHECK:
10078             if(!IS_SHOGI(gameInfo.variant))
10079                 strcat(parseList[boardIndex - 1], "+");
10080             break;
10081           case MT_CHECKMATE:
10082           case MT_STAINMATE:
10083             strcat(parseList[boardIndex - 1], "#");
10084             break;
10085         }
10086     }
10087 }
10088
10089
10090 /* Apply a move to the given board  */
10091 void
10092 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10093 {
10094   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10095   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10096
10097     /* [HGM] compute & store e.p. status and castling rights for new position */
10098     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10099
10100       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10101       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10102       board[EP_STATUS] = EP_NONE;
10103       board[EP_FILE] = board[EP_RANK] = 100;
10104
10105   if (fromY == DROP_RANK) {
10106         /* must be first */
10107         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10108             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10109             return;
10110         }
10111         piece = board[toY][toX] = (ChessSquare) fromX;
10112   } else {
10113 //      ChessSquare victim;
10114       int i;
10115
10116       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10117 //           victim = board[killY][killX],
10118            killed = board[killY][killX],
10119            board[killY][killX] = EmptySquare,
10120            board[EP_STATUS] = EP_CAPTURE;
10121            if( kill2X >= 0 && kill2Y >= 0)
10122              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10123       }
10124
10125       if( board[toY][toX] != EmptySquare ) {
10126            board[EP_STATUS] = EP_CAPTURE;
10127            if( (fromX != toX || fromY != toY) && // not igui!
10128                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10129                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10130                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10131            }
10132       }
10133
10134       pawn = board[fromY][fromX];
10135       if( pawn == WhiteLance || pawn == BlackLance ) {
10136            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10137                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10138                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10139            }
10140       }
10141       if( pawn == WhitePawn ) {
10142            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10143                board[EP_STATUS] = EP_PAWN_MOVE;
10144            if( toY-fromY>=2) {
10145                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10146                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10147                         gameInfo.variant != VariantBerolina || toX < fromX)
10148                       board[EP_STATUS] = toX | berolina;
10149                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10150                         gameInfo.variant != VariantBerolina || toX > fromX)
10151                       board[EP_STATUS] = toX;
10152            }
10153       } else
10154       if( pawn == BlackPawn ) {
10155            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10156                board[EP_STATUS] = EP_PAWN_MOVE;
10157            if( toY-fromY<= -2) {
10158                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10159                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10160                         gameInfo.variant != VariantBerolina || toX < fromX)
10161                       board[EP_STATUS] = toX | berolina;
10162                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10163                         gameInfo.variant != VariantBerolina || toX > fromX)
10164                       board[EP_STATUS] = toX;
10165            }
10166        }
10167
10168        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10169        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10170        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10171        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10172
10173        for(i=0; i<nrCastlingRights; i++) {
10174            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10175               board[CASTLING][i] == toX   && castlingRank[i] == toY
10176              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10177        }
10178
10179        if(gameInfo.variant == VariantSChess) { // update virginity
10180            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10181            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10182            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10183            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10184        }
10185
10186      if (fromX == toX && fromY == toY) return;
10187
10188      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10189      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10190      if(gameInfo.variant == VariantKnightmate)
10191          king += (int) WhiteUnicorn - (int) WhiteKing;
10192
10193     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10194        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10195         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10196         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10197         board[EP_STATUS] = EP_NONE; // capture was fake!
10198     } else
10199     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10200         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10201         board[toY][toX] = piece;
10202         board[EP_STATUS] = EP_NONE; // capture was fake!
10203     } else
10204     /* Code added by Tord: */
10205     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10206     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10207         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10208       board[EP_STATUS] = EP_NONE; // capture was fake!
10209       board[fromY][fromX] = EmptySquare;
10210       board[toY][toX] = EmptySquare;
10211       if((toX > fromX) != (piece == WhiteRook)) {
10212         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10213       } else {
10214         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10215       }
10216     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10217                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10218       board[EP_STATUS] = EP_NONE;
10219       board[fromY][fromX] = EmptySquare;
10220       board[toY][toX] = EmptySquare;
10221       if((toX > fromX) != (piece == BlackRook)) {
10222         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10223       } else {
10224         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10225       }
10226     /* End of code added by Tord */
10227
10228     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10229         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10230         board[toY][toX] = piece;
10231     } else if (board[fromY][fromX] == king
10232         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10233         && toY == fromY && toX > fromX+1) {
10234         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10235         board[fromY][toX-1] = board[fromY][rookX];
10236         board[fromY][rookX] = EmptySquare;
10237         board[fromY][fromX] = EmptySquare;
10238         board[toY][toX] = king;
10239     } else if (board[fromY][fromX] == king
10240         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10241                && toY == fromY && toX < fromX-1) {
10242         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10243         board[fromY][toX+1] = board[fromY][rookX];
10244         board[fromY][rookX] = EmptySquare;
10245         board[fromY][fromX] = EmptySquare;
10246         board[toY][toX] = king;
10247     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10248                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10249                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10250                ) {
10251         /* white pawn promotion */
10252         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10253         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10254             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10255         board[fromY][fromX] = EmptySquare;
10256     } else if ((fromY >= BOARD_HEIGHT>>1)
10257                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10258                && (toX != fromX)
10259                && gameInfo.variant != VariantXiangqi
10260                && gameInfo.variant != VariantBerolina
10261                && (pawn == WhitePawn)
10262                && (board[toY][toX] == EmptySquare)) {
10263         board[fromY][fromX] = EmptySquare;
10264         board[toY][toX] = piece;
10265         if(toY == epRank - 128 + 1)
10266             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10267         else
10268             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10269     } else if ((fromY == BOARD_HEIGHT-4)
10270                && (toX == fromX)
10271                && gameInfo.variant == VariantBerolina
10272                && (board[fromY][fromX] == WhitePawn)
10273                && (board[toY][toX] == EmptySquare)) {
10274         board[fromY][fromX] = EmptySquare;
10275         board[toY][toX] = WhitePawn;
10276         if(oldEP & EP_BEROLIN_A) {
10277                 captured = board[fromY][fromX-1];
10278                 board[fromY][fromX-1] = EmptySquare;
10279         }else{  captured = board[fromY][fromX+1];
10280                 board[fromY][fromX+1] = EmptySquare;
10281         }
10282     } else if (board[fromY][fromX] == king
10283         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10284                && toY == fromY && toX > fromX+1) {
10285         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10286         board[fromY][toX-1] = board[fromY][rookX];
10287         board[fromY][rookX] = EmptySquare;
10288         board[fromY][fromX] = EmptySquare;
10289         board[toY][toX] = king;
10290     } else if (board[fromY][fromX] == king
10291         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10292                && toY == fromY && toX < fromX-1) {
10293         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10294         board[fromY][toX+1] = board[fromY][rookX];
10295         board[fromY][rookX] = EmptySquare;
10296         board[fromY][fromX] = EmptySquare;
10297         board[toY][toX] = king;
10298     } else if (fromY == 7 && fromX == 3
10299                && board[fromY][fromX] == BlackKing
10300                && toY == 7 && toX == 5) {
10301         board[fromY][fromX] = EmptySquare;
10302         board[toY][toX] = BlackKing;
10303         board[fromY][7] = EmptySquare;
10304         board[toY][4] = BlackRook;
10305     } else if (fromY == 7 && fromX == 3
10306                && board[fromY][fromX] == BlackKing
10307                && toY == 7 && toX == 1) {
10308         board[fromY][fromX] = EmptySquare;
10309         board[toY][toX] = BlackKing;
10310         board[fromY][0] = EmptySquare;
10311         board[toY][2] = BlackRook;
10312     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10313                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10314                && toY < promoRank && promoChar
10315                ) {
10316         /* black pawn promotion */
10317         board[toY][toX] = CharToPiece(ToLower(promoChar));
10318         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10319             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10320         board[fromY][fromX] = EmptySquare;
10321     } else if ((fromY < BOARD_HEIGHT>>1)
10322                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10323                && (toX != fromX)
10324                && gameInfo.variant != VariantXiangqi
10325                && gameInfo.variant != VariantBerolina
10326                && (pawn == BlackPawn)
10327                && (board[toY][toX] == EmptySquare)) {
10328         board[fromY][fromX] = EmptySquare;
10329         board[toY][toX] = piece;
10330         if(toY == epRank - 128 - 1)
10331             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10332         else
10333             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10334     } else if ((fromY == 3)
10335                && (toX == fromX)
10336                && gameInfo.variant == VariantBerolina
10337                && (board[fromY][fromX] == BlackPawn)
10338                && (board[toY][toX] == EmptySquare)) {
10339         board[fromY][fromX] = EmptySquare;
10340         board[toY][toX] = BlackPawn;
10341         if(oldEP & EP_BEROLIN_A) {
10342                 captured = board[fromY][fromX-1];
10343                 board[fromY][fromX-1] = EmptySquare;
10344         }else{  captured = board[fromY][fromX+1];
10345                 board[fromY][fromX+1] = EmptySquare;
10346         }
10347     } else {
10348         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10349         board[fromY][fromX] = EmptySquare;
10350         board[toY][toX] = piece;
10351     }
10352   }
10353
10354     if (gameInfo.holdingsWidth != 0) {
10355
10356       /* !!A lot more code needs to be written to support holdings  */
10357       /* [HGM] OK, so I have written it. Holdings are stored in the */
10358       /* penultimate board files, so they are automaticlly stored   */
10359       /* in the game history.                                       */
10360       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10361                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10362         /* Delete from holdings, by decreasing count */
10363         /* and erasing image if necessary            */
10364         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10365         if(p < (int) BlackPawn) { /* white drop */
10366              p -= (int)WhitePawn;
10367                  p = PieceToNumber((ChessSquare)p);
10368              if(p >= gameInfo.holdingsSize) p = 0;
10369              if(--board[p][BOARD_WIDTH-2] <= 0)
10370                   board[p][BOARD_WIDTH-1] = EmptySquare;
10371              if((int)board[p][BOARD_WIDTH-2] < 0)
10372                         board[p][BOARD_WIDTH-2] = 0;
10373         } else {                  /* black drop */
10374              p -= (int)BlackPawn;
10375                  p = PieceToNumber((ChessSquare)p);
10376              if(p >= gameInfo.holdingsSize) p = 0;
10377              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10378                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10379              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10380                         board[BOARD_HEIGHT-1-p][1] = 0;
10381         }
10382       }
10383       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10384           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10385         /* [HGM] holdings: Add to holdings, if holdings exist */
10386         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10387                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10388                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10389         }
10390         p = (int) captured;
10391         if (p >= (int) BlackPawn) {
10392           p -= (int)BlackPawn;
10393           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10394                   /* Restore shogi-promoted piece to its original  first */
10395                   captured = (ChessSquare) (DEMOTED(captured));
10396                   p = DEMOTED(p);
10397           }
10398           p = PieceToNumber((ChessSquare)p);
10399           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10400           board[p][BOARD_WIDTH-2]++;
10401           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10402         } else {
10403           p -= (int)WhitePawn;
10404           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10405                   captured = (ChessSquare) (DEMOTED(captured));
10406                   p = DEMOTED(p);
10407           }
10408           p = PieceToNumber((ChessSquare)p);
10409           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10410           board[BOARD_HEIGHT-1-p][1]++;
10411           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10412         }
10413       }
10414     } else if (gameInfo.variant == VariantAtomic) {
10415       if (captured != EmptySquare) {
10416         int y, x;
10417         for (y = toY-1; y <= toY+1; y++) {
10418           for (x = toX-1; x <= toX+1; x++) {
10419             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10420                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10421               board[y][x] = EmptySquare;
10422             }
10423           }
10424         }
10425         board[toY][toX] = EmptySquare;
10426       }
10427     }
10428
10429     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10430         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10431     } else
10432     if(promoChar == '+') {
10433         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10434         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10435         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10436           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10437     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10438         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10439         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10440            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10441         board[toY][toX] = newPiece;
10442     }
10443     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10444                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10445         // [HGM] superchess: take promotion piece out of holdings
10446         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10447         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10448             if(!--board[k][BOARD_WIDTH-2])
10449                 board[k][BOARD_WIDTH-1] = EmptySquare;
10450         } else {
10451             if(!--board[BOARD_HEIGHT-1-k][1])
10452                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10453         }
10454     }
10455 }
10456
10457 /* Updates forwardMostMove */
10458 void
10459 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10460 {
10461     int x = toX, y = toY;
10462     char *s = parseList[forwardMostMove];
10463     ChessSquare p = boards[forwardMostMove][toY][toX];
10464 //    forwardMostMove++; // [HGM] bare: moved downstream
10465
10466     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10467     (void) CoordsToAlgebraic(boards[forwardMostMove],
10468                              PosFlags(forwardMostMove),
10469                              fromY, fromX, y, x, (killX < 0)*promoChar,
10470                              s);
10471     if(killX >= 0 && killY >= 0)
10472         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0', promoChar);
10473
10474     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10475         int timeLeft; static int lastLoadFlag=0; int king, piece;
10476         piece = boards[forwardMostMove][fromY][fromX];
10477         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10478         if(gameInfo.variant == VariantKnightmate)
10479             king += (int) WhiteUnicorn - (int) WhiteKing;
10480         if(forwardMostMove == 0) {
10481             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10482                 fprintf(serverMoves, "%s;", UserName());
10483             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10484                 fprintf(serverMoves, "%s;", second.tidy);
10485             fprintf(serverMoves, "%s;", first.tidy);
10486             if(gameMode == MachinePlaysWhite)
10487                 fprintf(serverMoves, "%s;", UserName());
10488             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10489                 fprintf(serverMoves, "%s;", second.tidy);
10490         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10491         lastLoadFlag = loadFlag;
10492         // print base move
10493         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10494         // print castling suffix
10495         if( toY == fromY && piece == king ) {
10496             if(toX-fromX > 1)
10497                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10498             if(fromX-toX >1)
10499                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10500         }
10501         // e.p. suffix
10502         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10503              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10504              boards[forwardMostMove][toY][toX] == EmptySquare
10505              && fromX != toX && fromY != toY)
10506                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10507         // promotion suffix
10508         if(promoChar != NULLCHAR) {
10509             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10510                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10511                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10512             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10513         }
10514         if(!loadFlag) {
10515                 char buf[MOVE_LEN*2], *p; int len;
10516             fprintf(serverMoves, "/%d/%d",
10517                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10518             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10519             else                      timeLeft = blackTimeRemaining/1000;
10520             fprintf(serverMoves, "/%d", timeLeft);
10521                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10522                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10523                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10524                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10525             fprintf(serverMoves, "/%s", buf);
10526         }
10527         fflush(serverMoves);
10528     }
10529
10530     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10531         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10532       return;
10533     }
10534     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10535     if (commentList[forwardMostMove+1] != NULL) {
10536         free(commentList[forwardMostMove+1]);
10537         commentList[forwardMostMove+1] = NULL;
10538     }
10539     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10540     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10541     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10542     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10543     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10544     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10545     adjustedClock = FALSE;
10546     gameInfo.result = GameUnfinished;
10547     if (gameInfo.resultDetails != NULL) {
10548         free(gameInfo.resultDetails);
10549         gameInfo.resultDetails = NULL;
10550     }
10551     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10552                               moveList[forwardMostMove - 1]);
10553     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10554       case MT_NONE:
10555       case MT_STALEMATE:
10556       default:
10557         break;
10558       case MT_CHECK:
10559         if(!IS_SHOGI(gameInfo.variant))
10560             strcat(parseList[forwardMostMove - 1], "+");
10561         break;
10562       case MT_CHECKMATE:
10563       case MT_STAINMATE:
10564         strcat(parseList[forwardMostMove - 1], "#");
10565         break;
10566     }
10567 }
10568
10569 /* Updates currentMove if not pausing */
10570 void
10571 ShowMove (int fromX, int fromY, int toX, int toY)
10572 {
10573     int instant = (gameMode == PlayFromGameFile) ?
10574         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10575     if(appData.noGUI) return;
10576     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10577         if (!instant) {
10578             if (forwardMostMove == currentMove + 1) {
10579                 AnimateMove(boards[forwardMostMove - 1],
10580                             fromX, fromY, toX, toY);
10581             }
10582         }
10583         currentMove = forwardMostMove;
10584     }
10585
10586     killX = killY = -1; // [HGM] lion: used up
10587
10588     if (instant) return;
10589
10590     DisplayMove(currentMove - 1);
10591     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10592             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10593                 SetHighlights(fromX, fromY, toX, toY);
10594             }
10595     }
10596     DrawPosition(FALSE, boards[currentMove]);
10597     DisplayBothClocks();
10598     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10599 }
10600
10601 void
10602 SendEgtPath (ChessProgramState *cps)
10603 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10604         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10605
10606         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10607
10608         while(*p) {
10609             char c, *q = name+1, *r, *s;
10610
10611             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10612             while(*p && *p != ',') *q++ = *p++;
10613             *q++ = ':'; *q = 0;
10614             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10615                 strcmp(name, ",nalimov:") == 0 ) {
10616                 // take nalimov path from the menu-changeable option first, if it is defined
10617               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10618                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10619             } else
10620             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10621                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10622                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10623                 s = r = StrStr(s, ":") + 1; // beginning of path info
10624                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10625                 c = *r; *r = 0;             // temporarily null-terminate path info
10626                     *--q = 0;               // strip of trailig ':' from name
10627                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10628                 *r = c;
10629                 SendToProgram(buf,cps);     // send egtbpath command for this format
10630             }
10631             if(*p == ',') p++; // read away comma to position for next format name
10632         }
10633 }
10634
10635 static int
10636 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10637 {
10638       int width = 8, height = 8, holdings = 0;             // most common sizes
10639       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10640       // correct the deviations default for each variant
10641       if( v == VariantXiangqi ) width = 9,  height = 10;
10642       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10643       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10644       if( v == VariantCapablanca || v == VariantCapaRandom ||
10645           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10646                                 width = 10;
10647       if( v == VariantCourier ) width = 12;
10648       if( v == VariantSuper )                            holdings = 8;
10649       if( v == VariantGreat )   width = 10,              holdings = 8;
10650       if( v == VariantSChess )                           holdings = 7;
10651       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10652       if( v == VariantChuChess) width = 10, height = 10;
10653       if( v == VariantChu )     width = 12, height = 12;
10654       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10655              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10656              holdingsSize >= 0 && holdingsSize != holdings;
10657 }
10658
10659 char variantError[MSG_SIZ];
10660
10661 char *
10662 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10663 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10664       char *p, *variant = VariantName(v);
10665       static char b[MSG_SIZ];
10666       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10667            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10668                                                holdingsSize, variant); // cook up sized variant name
10669            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10670            if(StrStr(list, b) == NULL) {
10671                // specific sized variant not known, check if general sizing allowed
10672                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10673                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10674                             boardWidth, boardHeight, holdingsSize, engine);
10675                    return NULL;
10676                }
10677                /* [HGM] here we really should compare with the maximum supported board size */
10678            }
10679       } else snprintf(b, MSG_SIZ,"%s", variant);
10680       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10681       p = StrStr(list, b);
10682       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10683       if(p == NULL) {
10684           // occurs not at all in list, or only as sub-string
10685           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10686           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10687               int l = strlen(variantError);
10688               char *q;
10689               while(p != list && p[-1] != ',') p--;
10690               q = strchr(p, ',');
10691               if(q) *q = NULLCHAR;
10692               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10693               if(q) *q= ',';
10694           }
10695           return NULL;
10696       }
10697       return b;
10698 }
10699
10700 void
10701 InitChessProgram (ChessProgramState *cps, int setup)
10702 /* setup needed to setup FRC opening position */
10703 {
10704     char buf[MSG_SIZ], *b;
10705     if (appData.noChessProgram) return;
10706     hintRequested = FALSE;
10707     bookRequested = FALSE;
10708
10709     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10710     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10711     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10712     if(cps->memSize) { /* [HGM] memory */
10713       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10714         SendToProgram(buf, cps);
10715     }
10716     SendEgtPath(cps); /* [HGM] EGT */
10717     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10718       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10719         SendToProgram(buf, cps);
10720     }
10721
10722     setboardSpoiledMachineBlack = FALSE;
10723     SendToProgram(cps->initString, cps);
10724     if (gameInfo.variant != VariantNormal &&
10725         gameInfo.variant != VariantLoadable
10726         /* [HGM] also send variant if board size non-standard */
10727         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10728
10729       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10730                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10731       if (b == NULL) {
10732         VariantClass v;
10733         char c, *q = cps->variants, *p = strchr(q, ',');
10734         if(p) *p = NULLCHAR;
10735         v = StringToVariant(q);
10736         DisplayError(variantError, 0);
10737         if(v != VariantUnknown && cps == &first) {
10738             int w, h, s;
10739             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10740                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10741             ASSIGN(appData.variant, q);
10742             Reset(TRUE, FALSE);
10743         }
10744         if(p) *p = ',';
10745         return;
10746       }
10747
10748       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10749       SendToProgram(buf, cps);
10750     }
10751     currentlyInitializedVariant = gameInfo.variant;
10752
10753     /* [HGM] send opening position in FRC to first engine */
10754     if(setup) {
10755           SendToProgram("force\n", cps);
10756           SendBoard(cps, 0);
10757           /* engine is now in force mode! Set flag to wake it up after first move. */
10758           setboardSpoiledMachineBlack = 1;
10759     }
10760
10761     if (cps->sendICS) {
10762       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10763       SendToProgram(buf, cps);
10764     }
10765     cps->maybeThinking = FALSE;
10766     cps->offeredDraw = 0;
10767     if (!appData.icsActive) {
10768         SendTimeControl(cps, movesPerSession, timeControl,
10769                         timeIncrement, appData.searchDepth,
10770                         searchTime);
10771     }
10772     if (appData.showThinking
10773         // [HGM] thinking: four options require thinking output to be sent
10774         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10775                                 ) {
10776         SendToProgram("post\n", cps);
10777     }
10778     SendToProgram("hard\n", cps);
10779     if (!appData.ponderNextMove) {
10780         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10781            it without being sure what state we are in first.  "hard"
10782            is not a toggle, so that one is OK.
10783          */
10784         SendToProgram("easy\n", cps);
10785     }
10786     if (cps->usePing) {
10787       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10788       SendToProgram(buf, cps);
10789     }
10790     cps->initDone = TRUE;
10791     ClearEngineOutputPane(cps == &second);
10792 }
10793
10794
10795 void
10796 ResendOptions (ChessProgramState *cps)
10797 { // send the stored value of the options
10798   int i;
10799   char buf[MSG_SIZ];
10800   Option *opt = cps->option;
10801   for(i=0; i<cps->nrOptions; i++, opt++) {
10802       switch(opt->type) {
10803         case Spin:
10804         case Slider:
10805         case CheckBox:
10806             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10807           break;
10808         case ComboBox:
10809           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10810           break;
10811         default:
10812             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10813           break;
10814         case Button:
10815         case SaveButton:
10816           continue;
10817       }
10818       SendToProgram(buf, cps);
10819   }
10820 }
10821
10822 void
10823 StartChessProgram (ChessProgramState *cps)
10824 {
10825     char buf[MSG_SIZ];
10826     int err;
10827
10828     if (appData.noChessProgram) return;
10829     cps->initDone = FALSE;
10830
10831     if (strcmp(cps->host, "localhost") == 0) {
10832         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10833     } else if (*appData.remoteShell == NULLCHAR) {
10834         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10835     } else {
10836         if (*appData.remoteUser == NULLCHAR) {
10837           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10838                     cps->program);
10839         } else {
10840           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10841                     cps->host, appData.remoteUser, cps->program);
10842         }
10843         err = StartChildProcess(buf, "", &cps->pr);
10844     }
10845
10846     if (err != 0) {
10847       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10848         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10849         if(cps != &first) return;
10850         appData.noChessProgram = TRUE;
10851         ThawUI();
10852         SetNCPMode();
10853 //      DisplayFatalError(buf, err, 1);
10854 //      cps->pr = NoProc;
10855 //      cps->isr = NULL;
10856         return;
10857     }
10858
10859     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10860     if (cps->protocolVersion > 1) {
10861       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10862       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10863         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10864         cps->comboCnt = 0;  //                and values of combo boxes
10865       }
10866       SendToProgram(buf, cps);
10867       if(cps->reload) ResendOptions(cps);
10868     } else {
10869       SendToProgram("xboard\n", cps);
10870     }
10871 }
10872
10873 void
10874 TwoMachinesEventIfReady P((void))
10875 {
10876   static int curMess = 0;
10877   if (first.lastPing != first.lastPong) {
10878     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10879     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10880     return;
10881   }
10882   if (second.lastPing != second.lastPong) {
10883     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10884     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10885     return;
10886   }
10887   DisplayMessage("", ""); curMess = 0;
10888   TwoMachinesEvent();
10889 }
10890
10891 char *
10892 MakeName (char *template)
10893 {
10894     time_t clock;
10895     struct tm *tm;
10896     static char buf[MSG_SIZ];
10897     char *p = buf;
10898     int i;
10899
10900     clock = time((time_t *)NULL);
10901     tm = localtime(&clock);
10902
10903     while(*p++ = *template++) if(p[-1] == '%') {
10904         switch(*template++) {
10905           case 0:   *p = 0; return buf;
10906           case 'Y': i = tm->tm_year+1900; break;
10907           case 'y': i = tm->tm_year-100; break;
10908           case 'M': i = tm->tm_mon+1; break;
10909           case 'd': i = tm->tm_mday; break;
10910           case 'h': i = tm->tm_hour; break;
10911           case 'm': i = tm->tm_min; break;
10912           case 's': i = tm->tm_sec; break;
10913           default:  i = 0;
10914         }
10915         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10916     }
10917     return buf;
10918 }
10919
10920 int
10921 CountPlayers (char *p)
10922 {
10923     int n = 0;
10924     while(p = strchr(p, '\n')) p++, n++; // count participants
10925     return n;
10926 }
10927
10928 FILE *
10929 WriteTourneyFile (char *results, FILE *f)
10930 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10931     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10932     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10933         // create a file with tournament description
10934         fprintf(f, "-participants {%s}\n", appData.participants);
10935         fprintf(f, "-seedBase %d\n", appData.seedBase);
10936         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10937         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10938         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10939         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10940         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10941         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10942         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10943         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10944         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10945         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10946         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10947         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10948         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10949         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10950         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10951         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10952         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10953         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10954         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10955         fprintf(f, "-smpCores %d\n", appData.smpCores);
10956         if(searchTime > 0)
10957                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10958         else {
10959                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10960                 fprintf(f, "-tc %s\n", appData.timeControl);
10961                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10962         }
10963         fprintf(f, "-results \"%s\"\n", results);
10964     }
10965     return f;
10966 }
10967
10968 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10969
10970 void
10971 Substitute (char *participants, int expunge)
10972 {
10973     int i, changed, changes=0, nPlayers=0;
10974     char *p, *q, *r, buf[MSG_SIZ];
10975     if(participants == NULL) return;
10976     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10977     r = p = participants; q = appData.participants;
10978     while(*p && *p == *q) {
10979         if(*p == '\n') r = p+1, nPlayers++;
10980         p++; q++;
10981     }
10982     if(*p) { // difference
10983         while(*p && *p++ != '\n');
10984         while(*q && *q++ != '\n');
10985       changed = nPlayers;
10986         changes = 1 + (strcmp(p, q) != 0);
10987     }
10988     if(changes == 1) { // a single engine mnemonic was changed
10989         q = r; while(*q) nPlayers += (*q++ == '\n');
10990         p = buf; while(*r && (*p = *r++) != '\n') p++;
10991         *p = NULLCHAR;
10992         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10993         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10994         if(mnemonic[i]) { // The substitute is valid
10995             FILE *f;
10996             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10997                 flock(fileno(f), LOCK_EX);
10998                 ParseArgsFromFile(f);
10999                 fseek(f, 0, SEEK_SET);
11000                 FREE(appData.participants); appData.participants = participants;
11001                 if(expunge) { // erase results of replaced engine
11002                     int len = strlen(appData.results), w, b, dummy;
11003                     for(i=0; i<len; i++) {
11004                         Pairing(i, nPlayers, &w, &b, &dummy);
11005                         if((w == changed || b == changed) && appData.results[i] == '*') {
11006                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11007                             fclose(f);
11008                             return;
11009                         }
11010                     }
11011                     for(i=0; i<len; i++) {
11012                         Pairing(i, nPlayers, &w, &b, &dummy);
11013                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11014                     }
11015                 }
11016                 WriteTourneyFile(appData.results, f);
11017                 fclose(f); // release lock
11018                 return;
11019             }
11020         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11021     }
11022     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11023     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11024     free(participants);
11025     return;
11026 }
11027
11028 int
11029 CheckPlayers (char *participants)
11030 {
11031         int i;
11032         char buf[MSG_SIZ], *p;
11033         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11034         while(p = strchr(participants, '\n')) {
11035             *p = NULLCHAR;
11036             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11037             if(!mnemonic[i]) {
11038                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11039                 *p = '\n';
11040                 DisplayError(buf, 0);
11041                 return 1;
11042             }
11043             *p = '\n';
11044             participants = p + 1;
11045         }
11046         return 0;
11047 }
11048
11049 int
11050 CreateTourney (char *name)
11051 {
11052         FILE *f;
11053         if(matchMode && strcmp(name, appData.tourneyFile)) {
11054              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11055         }
11056         if(name[0] == NULLCHAR) {
11057             if(appData.participants[0])
11058                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11059             return 0;
11060         }
11061         f = fopen(name, "r");
11062         if(f) { // file exists
11063             ASSIGN(appData.tourneyFile, name);
11064             ParseArgsFromFile(f); // parse it
11065         } else {
11066             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11067             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11068                 DisplayError(_("Not enough participants"), 0);
11069                 return 0;
11070             }
11071             if(CheckPlayers(appData.participants)) return 0;
11072             ASSIGN(appData.tourneyFile, name);
11073             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11074             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11075         }
11076         fclose(f);
11077         appData.noChessProgram = FALSE;
11078         appData.clockMode = TRUE;
11079         SetGNUMode();
11080         return 1;
11081 }
11082
11083 int
11084 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11085 {
11086     char buf[MSG_SIZ], *p, *q;
11087     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11088     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11089     skip = !all && group[0]; // if group requested, we start in skip mode
11090     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11091         p = names; q = buf; header = 0;
11092         while(*p && *p != '\n') *q++ = *p++;
11093         *q = 0;
11094         if(*p == '\n') p++;
11095         if(buf[0] == '#') {
11096             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11097             depth++; // we must be entering a new group
11098             if(all) continue; // suppress printing group headers when complete list requested
11099             header = 1;
11100             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11101         }
11102         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11103         if(engineList[i]) free(engineList[i]);
11104         engineList[i] = strdup(buf);
11105         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11106         if(engineMnemonic[i]) free(engineMnemonic[i]);
11107         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11108             strcat(buf, " (");
11109             sscanf(q + 8, "%s", buf + strlen(buf));
11110             strcat(buf, ")");
11111         }
11112         engineMnemonic[i] = strdup(buf);
11113         i++;
11114     }
11115     engineList[i] = engineMnemonic[i] = NULL;
11116     return i;
11117 }
11118
11119 // following implemented as macro to avoid type limitations
11120 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11121
11122 void
11123 SwapEngines (int n)
11124 {   // swap settings for first engine and other engine (so far only some selected options)
11125     int h;
11126     char *p;
11127     if(n == 0) return;
11128     SWAP(directory, p)
11129     SWAP(chessProgram, p)
11130     SWAP(isUCI, h)
11131     SWAP(hasOwnBookUCI, h)
11132     SWAP(protocolVersion, h)
11133     SWAP(reuse, h)
11134     SWAP(scoreIsAbsolute, h)
11135     SWAP(timeOdds, h)
11136     SWAP(logo, p)
11137     SWAP(pgnName, p)
11138     SWAP(pvSAN, h)
11139     SWAP(engOptions, p)
11140     SWAP(engInitString, p)
11141     SWAP(computerString, p)
11142     SWAP(features, p)
11143     SWAP(fenOverride, p)
11144     SWAP(NPS, h)
11145     SWAP(accumulateTC, h)
11146     SWAP(drawDepth, h)
11147     SWAP(host, p)
11148     SWAP(pseudo, h)
11149 }
11150
11151 int
11152 GetEngineLine (char *s, int n)
11153 {
11154     int i;
11155     char buf[MSG_SIZ];
11156     extern char *icsNames;
11157     if(!s || !*s) return 0;
11158     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11159     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11160     if(!mnemonic[i]) return 0;
11161     if(n == 11) return 1; // just testing if there was a match
11162     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11163     if(n == 1) SwapEngines(n);
11164     ParseArgsFromString(buf);
11165     if(n == 1) SwapEngines(n);
11166     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11167         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11168         ParseArgsFromString(buf);
11169     }
11170     return 1;
11171 }
11172
11173 int
11174 SetPlayer (int player, char *p)
11175 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11176     int i;
11177     char buf[MSG_SIZ], *engineName;
11178     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11179     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11180     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11181     if(mnemonic[i]) {
11182         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11183         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11184         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11185         ParseArgsFromString(buf);
11186     } else { // no engine with this nickname is installed!
11187         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11188         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11189         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11190         ModeHighlight();
11191         DisplayError(buf, 0);
11192         return 0;
11193     }
11194     free(engineName);
11195     return i;
11196 }
11197
11198 char *recentEngines;
11199
11200 void
11201 RecentEngineEvent (int nr)
11202 {
11203     int n;
11204 //    SwapEngines(1); // bump first to second
11205 //    ReplaceEngine(&second, 1); // and load it there
11206     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11207     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11208     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11209         ReplaceEngine(&first, 0);
11210         FloatToFront(&appData.recentEngineList, command[n]);
11211     }
11212 }
11213
11214 int
11215 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11216 {   // determine players from game number
11217     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11218
11219     if(appData.tourneyType == 0) {
11220         roundsPerCycle = (nPlayers - 1) | 1;
11221         pairingsPerRound = nPlayers / 2;
11222     } else if(appData.tourneyType > 0) {
11223         roundsPerCycle = nPlayers - appData.tourneyType;
11224         pairingsPerRound = appData.tourneyType;
11225     }
11226     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11227     gamesPerCycle = gamesPerRound * roundsPerCycle;
11228     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11229     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11230     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11231     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11232     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11233     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11234
11235     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11236     if(appData.roundSync) *syncInterval = gamesPerRound;
11237
11238     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11239
11240     if(appData.tourneyType == 0) {
11241         if(curPairing == (nPlayers-1)/2 ) {
11242             *whitePlayer = curRound;
11243             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11244         } else {
11245             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11246             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11247             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11248             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11249         }
11250     } else if(appData.tourneyType > 1) {
11251         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11252         *whitePlayer = curRound + appData.tourneyType;
11253     } else if(appData.tourneyType > 0) {
11254         *whitePlayer = curPairing;
11255         *blackPlayer = curRound + appData.tourneyType;
11256     }
11257
11258     // take care of white/black alternation per round.
11259     // For cycles and games this is already taken care of by default, derived from matchGame!
11260     return curRound & 1;
11261 }
11262
11263 int
11264 NextTourneyGame (int nr, int *swapColors)
11265 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11266     char *p, *q;
11267     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11268     FILE *tf;
11269     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11270     tf = fopen(appData.tourneyFile, "r");
11271     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11272     ParseArgsFromFile(tf); fclose(tf);
11273     InitTimeControls(); // TC might be altered from tourney file
11274
11275     nPlayers = CountPlayers(appData.participants); // count participants
11276     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11277     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11278
11279     if(syncInterval) {
11280         p = q = appData.results;
11281         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11282         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11283             DisplayMessage(_("Waiting for other game(s)"),"");
11284             waitingForGame = TRUE;
11285             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11286             return 0;
11287         }
11288         waitingForGame = FALSE;
11289     }
11290
11291     if(appData.tourneyType < 0) {
11292         if(nr>=0 && !pairingReceived) {
11293             char buf[1<<16];
11294             if(pairing.pr == NoProc) {
11295                 if(!appData.pairingEngine[0]) {
11296                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11297                     return 0;
11298                 }
11299                 StartChessProgram(&pairing); // starts the pairing engine
11300             }
11301             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11302             SendToProgram(buf, &pairing);
11303             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11304             SendToProgram(buf, &pairing);
11305             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11306         }
11307         pairingReceived = 0;                              // ... so we continue here
11308         *swapColors = 0;
11309         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11310         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11311         matchGame = 1; roundNr = nr / syncInterval + 1;
11312     }
11313
11314     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11315
11316     // redefine engines, engine dir, etc.
11317     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11318     if(first.pr == NoProc) {
11319       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11320       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11321     }
11322     if(second.pr == NoProc) {
11323       SwapEngines(1);
11324       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11325       SwapEngines(1);         // and make that valid for second engine by swapping
11326       InitEngine(&second, 1);
11327     }
11328     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11329     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11330     return OK;
11331 }
11332
11333 void
11334 NextMatchGame ()
11335 {   // performs game initialization that does not invoke engines, and then tries to start the game
11336     int res, firstWhite, swapColors = 0;
11337     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11338     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
11339         char buf[MSG_SIZ];
11340         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11341         if(strcmp(buf, currentDebugFile)) { // name has changed
11342             FILE *f = fopen(buf, "w");
11343             if(f) { // if opening the new file failed, just keep using the old one
11344                 ASSIGN(currentDebugFile, buf);
11345                 fclose(debugFP);
11346                 debugFP = f;
11347             }
11348             if(appData.serverFileName) {
11349                 if(serverFP) fclose(serverFP);
11350                 serverFP = fopen(appData.serverFileName, "w");
11351                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11352                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11353             }
11354         }
11355     }
11356     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11357     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11358     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11359     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11360     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11361     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11362     Reset(FALSE, first.pr != NoProc);
11363     res = LoadGameOrPosition(matchGame); // setup game
11364     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11365     if(!res) return; // abort when bad game/pos file
11366     TwoMachinesEvent();
11367 }
11368
11369 void
11370 UserAdjudicationEvent (int result)
11371 {
11372     ChessMove gameResult = GameIsDrawn;
11373
11374     if( result > 0 ) {
11375         gameResult = WhiteWins;
11376     }
11377     else if( result < 0 ) {
11378         gameResult = BlackWins;
11379     }
11380
11381     if( gameMode == TwoMachinesPlay ) {
11382         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11383     }
11384 }
11385
11386
11387 // [HGM] save: calculate checksum of game to make games easily identifiable
11388 int
11389 StringCheckSum (char *s)
11390 {
11391         int i = 0;
11392         if(s==NULL) return 0;
11393         while(*s) i = i*259 + *s++;
11394         return i;
11395 }
11396
11397 int
11398 GameCheckSum ()
11399 {
11400         int i, sum=0;
11401         for(i=backwardMostMove; i<forwardMostMove; i++) {
11402                 sum += pvInfoList[i].depth;
11403                 sum += StringCheckSum(parseList[i]);
11404                 sum += StringCheckSum(commentList[i]);
11405                 sum *= 261;
11406         }
11407         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11408         return sum + StringCheckSum(commentList[i]);
11409 } // end of save patch
11410
11411 void
11412 GameEnds (ChessMove result, char *resultDetails, int whosays)
11413 {
11414     GameMode nextGameMode;
11415     int isIcsGame;
11416     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11417
11418     if(endingGame) return; /* [HGM] crash: forbid recursion */
11419     endingGame = 1;
11420     if(twoBoards) { // [HGM] dual: switch back to one board
11421         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11422         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11423     }
11424     if (appData.debugMode) {
11425       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11426               result, resultDetails ? resultDetails : "(null)", whosays);
11427     }
11428
11429     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11430
11431     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11432
11433     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11434         /* If we are playing on ICS, the server decides when the
11435            game is over, but the engine can offer to draw, claim
11436            a draw, or resign.
11437          */
11438 #if ZIPPY
11439         if (appData.zippyPlay && first.initDone) {
11440             if (result == GameIsDrawn) {
11441                 /* In case draw still needs to be claimed */
11442                 SendToICS(ics_prefix);
11443                 SendToICS("draw\n");
11444             } else if (StrCaseStr(resultDetails, "resign")) {
11445                 SendToICS(ics_prefix);
11446                 SendToICS("resign\n");
11447             }
11448         }
11449 #endif
11450         endingGame = 0; /* [HGM] crash */
11451         return;
11452     }
11453
11454     /* If we're loading the game from a file, stop */
11455     if (whosays == GE_FILE) {
11456       (void) StopLoadGameTimer();
11457       gameFileFP = NULL;
11458     }
11459
11460     /* Cancel draw offers */
11461     first.offeredDraw = second.offeredDraw = 0;
11462
11463     /* If this is an ICS game, only ICS can really say it's done;
11464        if not, anyone can. */
11465     isIcsGame = (gameMode == IcsPlayingWhite ||
11466                  gameMode == IcsPlayingBlack ||
11467                  gameMode == IcsObserving    ||
11468                  gameMode == IcsExamining);
11469
11470     if (!isIcsGame || whosays == GE_ICS) {
11471         /* OK -- not an ICS game, or ICS said it was done */
11472         StopClocks();
11473         if (!isIcsGame && !appData.noChessProgram)
11474           SetUserThinkingEnables();
11475
11476         /* [HGM] if a machine claims the game end we verify this claim */
11477         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11478             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11479                 char claimer;
11480                 ChessMove trueResult = (ChessMove) -1;
11481
11482                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11483                                             first.twoMachinesColor[0] :
11484                                             second.twoMachinesColor[0] ;
11485
11486                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11487                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11488                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11489                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11490                 } else
11491                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11492                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11493                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11494                 } else
11495                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11496                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11497                 }
11498
11499                 // now verify win claims, but not in drop games, as we don't understand those yet
11500                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11501                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11502                     (result == WhiteWins && claimer == 'w' ||
11503                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11504                       if (appData.debugMode) {
11505                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11506                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11507                       }
11508                       if(result != trueResult) {
11509                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11510                               result = claimer == 'w' ? BlackWins : WhiteWins;
11511                               resultDetails = buf;
11512                       }
11513                 } else
11514                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11515                     && (forwardMostMove <= backwardMostMove ||
11516                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11517                         (claimer=='b')==(forwardMostMove&1))
11518                                                                                   ) {
11519                       /* [HGM] verify: draws that were not flagged are false claims */
11520                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11521                       result = claimer == 'w' ? BlackWins : WhiteWins;
11522                       resultDetails = buf;
11523                 }
11524                 /* (Claiming a loss is accepted no questions asked!) */
11525             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11526                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11527                 result = GameUnfinished;
11528                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11529             }
11530             /* [HGM] bare: don't allow bare King to win */
11531             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11532                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11533                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11534                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11535                && result != GameIsDrawn)
11536             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11537                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11538                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11539                         if(p >= 0 && p <= (int)WhiteKing) k++;
11540                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11541                 }
11542                 if (appData.debugMode) {
11543                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11544                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11545                 }
11546                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11547                         result = GameIsDrawn;
11548                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11549                         resultDetails = buf;
11550                 }
11551             }
11552         }
11553
11554
11555         if(serverMoves != NULL && !loadFlag) { char c = '=';
11556             if(result==WhiteWins) c = '+';
11557             if(result==BlackWins) c = '-';
11558             if(resultDetails != NULL)
11559                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11560         }
11561         if (resultDetails != NULL) {
11562             gameInfo.result = result;
11563             gameInfo.resultDetails = StrSave(resultDetails);
11564
11565             /* display last move only if game was not loaded from file */
11566             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11567                 DisplayMove(currentMove - 1);
11568
11569             if (forwardMostMove != 0) {
11570                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11571                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11572                                                                 ) {
11573                     if (*appData.saveGameFile != NULLCHAR) {
11574                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11575                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11576                         else
11577                         SaveGameToFile(appData.saveGameFile, TRUE);
11578                     } else if (appData.autoSaveGames) {
11579                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11580                     }
11581                     if (*appData.savePositionFile != NULLCHAR) {
11582                         SavePositionToFile(appData.savePositionFile);
11583                     }
11584                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11585                 }
11586             }
11587
11588             /* Tell program how game ended in case it is learning */
11589             /* [HGM] Moved this to after saving the PGN, just in case */
11590             /* engine died and we got here through time loss. In that */
11591             /* case we will get a fatal error writing the pipe, which */
11592             /* would otherwise lose us the PGN.                       */
11593             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11594             /* output during GameEnds should never be fatal anymore   */
11595             if (gameMode == MachinePlaysWhite ||
11596                 gameMode == MachinePlaysBlack ||
11597                 gameMode == TwoMachinesPlay ||
11598                 gameMode == IcsPlayingWhite ||
11599                 gameMode == IcsPlayingBlack ||
11600                 gameMode == BeginningOfGame) {
11601                 char buf[MSG_SIZ];
11602                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11603                         resultDetails);
11604                 if (first.pr != NoProc) {
11605                     SendToProgram(buf, &first);
11606                 }
11607                 if (second.pr != NoProc &&
11608                     gameMode == TwoMachinesPlay) {
11609                     SendToProgram(buf, &second);
11610                 }
11611             }
11612         }
11613
11614         if (appData.icsActive) {
11615             if (appData.quietPlay &&
11616                 (gameMode == IcsPlayingWhite ||
11617                  gameMode == IcsPlayingBlack)) {
11618                 SendToICS(ics_prefix);
11619                 SendToICS("set shout 1\n");
11620             }
11621             nextGameMode = IcsIdle;
11622             ics_user_moved = FALSE;
11623             /* clean up premove.  It's ugly when the game has ended and the
11624              * premove highlights are still on the board.
11625              */
11626             if (gotPremove) {
11627               gotPremove = FALSE;
11628               ClearPremoveHighlights();
11629               DrawPosition(FALSE, boards[currentMove]);
11630             }
11631             if (whosays == GE_ICS) {
11632                 switch (result) {
11633                 case WhiteWins:
11634                     if (gameMode == IcsPlayingWhite)
11635                         PlayIcsWinSound();
11636                     else if(gameMode == IcsPlayingBlack)
11637                         PlayIcsLossSound();
11638                     break;
11639                 case BlackWins:
11640                     if (gameMode == IcsPlayingBlack)
11641                         PlayIcsWinSound();
11642                     else if(gameMode == IcsPlayingWhite)
11643                         PlayIcsLossSound();
11644                     break;
11645                 case GameIsDrawn:
11646                     PlayIcsDrawSound();
11647                     break;
11648                 default:
11649                     PlayIcsUnfinishedSound();
11650                 }
11651             }
11652             if(appData.quitNext) { ExitEvent(0); return; }
11653         } else if (gameMode == EditGame ||
11654                    gameMode == PlayFromGameFile ||
11655                    gameMode == AnalyzeMode ||
11656                    gameMode == AnalyzeFile) {
11657             nextGameMode = gameMode;
11658         } else {
11659             nextGameMode = EndOfGame;
11660         }
11661         pausing = FALSE;
11662         ModeHighlight();
11663     } else {
11664         nextGameMode = gameMode;
11665     }
11666
11667     if (appData.noChessProgram) {
11668         gameMode = nextGameMode;
11669         ModeHighlight();
11670         endingGame = 0; /* [HGM] crash */
11671         return;
11672     }
11673
11674     if (first.reuse) {
11675         /* Put first chess program into idle state */
11676         if (first.pr != NoProc &&
11677             (gameMode == MachinePlaysWhite ||
11678              gameMode == MachinePlaysBlack ||
11679              gameMode == TwoMachinesPlay ||
11680              gameMode == IcsPlayingWhite ||
11681              gameMode == IcsPlayingBlack ||
11682              gameMode == BeginningOfGame)) {
11683             SendToProgram("force\n", &first);
11684             if (first.usePing) {
11685               char buf[MSG_SIZ];
11686               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11687               SendToProgram(buf, &first);
11688             }
11689         }
11690     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11691         /* Kill off first chess program */
11692         if (first.isr != NULL)
11693           RemoveInputSource(first.isr);
11694         first.isr = NULL;
11695
11696         if (first.pr != NoProc) {
11697             ExitAnalyzeMode();
11698             DoSleep( appData.delayBeforeQuit );
11699             SendToProgram("quit\n", &first);
11700             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11701             first.reload = TRUE;
11702         }
11703         first.pr = NoProc;
11704     }
11705     if (second.reuse) {
11706         /* Put second chess program into idle state */
11707         if (second.pr != NoProc &&
11708             gameMode == TwoMachinesPlay) {
11709             SendToProgram("force\n", &second);
11710             if (second.usePing) {
11711               char buf[MSG_SIZ];
11712               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11713               SendToProgram(buf, &second);
11714             }
11715         }
11716     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11717         /* Kill off second chess program */
11718         if (second.isr != NULL)
11719           RemoveInputSource(second.isr);
11720         second.isr = NULL;
11721
11722         if (second.pr != NoProc) {
11723             DoSleep( appData.delayBeforeQuit );
11724             SendToProgram("quit\n", &second);
11725             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11726             second.reload = TRUE;
11727         }
11728         second.pr = NoProc;
11729     }
11730
11731     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11732         char resChar = '=';
11733         switch (result) {
11734         case WhiteWins:
11735           resChar = '+';
11736           if (first.twoMachinesColor[0] == 'w') {
11737             first.matchWins++;
11738           } else {
11739             second.matchWins++;
11740           }
11741           break;
11742         case BlackWins:
11743           resChar = '-';
11744           if (first.twoMachinesColor[0] == 'b') {
11745             first.matchWins++;
11746           } else {
11747             second.matchWins++;
11748           }
11749           break;
11750         case GameUnfinished:
11751           resChar = ' ';
11752         default:
11753           break;
11754         }
11755
11756         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11757         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11758             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11759             ReserveGame(nextGame, resChar); // sets nextGame
11760             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11761             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11762         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11763
11764         if (nextGame <= appData.matchGames && !abortMatch) {
11765             gameMode = nextGameMode;
11766             matchGame = nextGame; // this will be overruled in tourney mode!
11767             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11768             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11769             endingGame = 0; /* [HGM] crash */
11770             return;
11771         } else {
11772             gameMode = nextGameMode;
11773             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11774                      first.tidy, second.tidy,
11775                      first.matchWins, second.matchWins,
11776                      appData.matchGames - (first.matchWins + second.matchWins));
11777             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11778             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11779             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11780             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11781                 first.twoMachinesColor = "black\n";
11782                 second.twoMachinesColor = "white\n";
11783             } else {
11784                 first.twoMachinesColor = "white\n";
11785                 second.twoMachinesColor = "black\n";
11786             }
11787         }
11788     }
11789     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11790         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11791       ExitAnalyzeMode();
11792     gameMode = nextGameMode;
11793     ModeHighlight();
11794     endingGame = 0;  /* [HGM] crash */
11795     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11796         if(matchMode == TRUE) { // match through command line: exit with or without popup
11797             if(ranking) {
11798                 ToNrEvent(forwardMostMove);
11799                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11800                 else ExitEvent(0);
11801             } else DisplayFatalError(buf, 0, 0);
11802         } else { // match through menu; just stop, with or without popup
11803             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11804             ModeHighlight();
11805             if(ranking){
11806                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11807             } else DisplayNote(buf);
11808       }
11809       if(ranking) free(ranking);
11810     }
11811 }
11812
11813 /* Assumes program was just initialized (initString sent).
11814    Leaves program in force mode. */
11815 void
11816 FeedMovesToProgram (ChessProgramState *cps, int upto)
11817 {
11818     int i;
11819
11820     if (appData.debugMode)
11821       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11822               startedFromSetupPosition ? "position and " : "",
11823               backwardMostMove, upto, cps->which);
11824     if(currentlyInitializedVariant != gameInfo.variant) {
11825       char buf[MSG_SIZ];
11826         // [HGM] variantswitch: make engine aware of new variant
11827         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11828                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11829                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11830         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11831         SendToProgram(buf, cps);
11832         currentlyInitializedVariant = gameInfo.variant;
11833     }
11834     SendToProgram("force\n", cps);
11835     if (startedFromSetupPosition) {
11836         SendBoard(cps, backwardMostMove);
11837     if (appData.debugMode) {
11838         fprintf(debugFP, "feedMoves\n");
11839     }
11840     }
11841     for (i = backwardMostMove; i < upto; i++) {
11842         SendMoveToProgram(i, cps);
11843     }
11844 }
11845
11846
11847 int
11848 ResurrectChessProgram ()
11849 {
11850      /* The chess program may have exited.
11851         If so, restart it and feed it all the moves made so far. */
11852     static int doInit = 0;
11853
11854     if (appData.noChessProgram) return 1;
11855
11856     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11857         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11858         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11859         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11860     } else {
11861         if (first.pr != NoProc) return 1;
11862         StartChessProgram(&first);
11863     }
11864     InitChessProgram(&first, FALSE);
11865     FeedMovesToProgram(&first, currentMove);
11866
11867     if (!first.sendTime) {
11868         /* can't tell gnuchess what its clock should read,
11869            so we bow to its notion. */
11870         ResetClocks();
11871         timeRemaining[0][currentMove] = whiteTimeRemaining;
11872         timeRemaining[1][currentMove] = blackTimeRemaining;
11873     }
11874
11875     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11876                 appData.icsEngineAnalyze) && first.analysisSupport) {
11877       SendToProgram("analyze\n", &first);
11878       first.analyzing = TRUE;
11879     }
11880     return 1;
11881 }
11882
11883 /*
11884  * Button procedures
11885  */
11886 void
11887 Reset (int redraw, int init)
11888 {
11889     int i;
11890
11891     if (appData.debugMode) {
11892         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11893                 redraw, init, gameMode);
11894     }
11895     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11896     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11897     CleanupTail(); // [HGM] vari: delete any stored variations
11898     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11899     pausing = pauseExamInvalid = FALSE;
11900     startedFromSetupPosition = blackPlaysFirst = FALSE;
11901     firstMove = TRUE;
11902     whiteFlag = blackFlag = FALSE;
11903     userOfferedDraw = FALSE;
11904     hintRequested = bookRequested = FALSE;
11905     first.maybeThinking = FALSE;
11906     second.maybeThinking = FALSE;
11907     first.bookSuspend = FALSE; // [HGM] book
11908     second.bookSuspend = FALSE;
11909     thinkOutput[0] = NULLCHAR;
11910     lastHint[0] = NULLCHAR;
11911     ClearGameInfo(&gameInfo);
11912     gameInfo.variant = StringToVariant(appData.variant);
11913     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11914     ics_user_moved = ics_clock_paused = FALSE;
11915     ics_getting_history = H_FALSE;
11916     ics_gamenum = -1;
11917     white_holding[0] = black_holding[0] = NULLCHAR;
11918     ClearProgramStats();
11919     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11920
11921     ResetFrontEnd();
11922     ClearHighlights();
11923     flipView = appData.flipView;
11924     ClearPremoveHighlights();
11925     gotPremove = FALSE;
11926     alarmSounded = FALSE;
11927     killX = killY = -1; // [HGM] lion
11928
11929     GameEnds(EndOfFile, NULL, GE_PLAYER);
11930     if(appData.serverMovesName != NULL) {
11931         /* [HGM] prepare to make moves file for broadcasting */
11932         clock_t t = clock();
11933         if(serverMoves != NULL) fclose(serverMoves);
11934         serverMoves = fopen(appData.serverMovesName, "r");
11935         if(serverMoves != NULL) {
11936             fclose(serverMoves);
11937             /* delay 15 sec before overwriting, so all clients can see end */
11938             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11939         }
11940         serverMoves = fopen(appData.serverMovesName, "w");
11941     }
11942
11943     ExitAnalyzeMode();
11944     gameMode = BeginningOfGame;
11945     ModeHighlight();
11946     if(appData.icsActive) gameInfo.variant = VariantNormal;
11947     currentMove = forwardMostMove = backwardMostMove = 0;
11948     MarkTargetSquares(1);
11949     InitPosition(redraw);
11950     for (i = 0; i < MAX_MOVES; i++) {
11951         if (commentList[i] != NULL) {
11952             free(commentList[i]);
11953             commentList[i] = NULL;
11954         }
11955     }
11956     ResetClocks();
11957     timeRemaining[0][0] = whiteTimeRemaining;
11958     timeRemaining[1][0] = blackTimeRemaining;
11959
11960     if (first.pr == NoProc) {
11961         StartChessProgram(&first);
11962     }
11963     if (init) {
11964             InitChessProgram(&first, startedFromSetupPosition);
11965     }
11966     DisplayTitle("");
11967     DisplayMessage("", "");
11968     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11969     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11970     ClearMap();        // [HGM] exclude: invalidate map
11971 }
11972
11973 void
11974 AutoPlayGameLoop ()
11975 {
11976     for (;;) {
11977         if (!AutoPlayOneMove())
11978           return;
11979         if (matchMode || appData.timeDelay == 0)
11980           continue;
11981         if (appData.timeDelay < 0)
11982           return;
11983         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11984         break;
11985     }
11986 }
11987
11988 void
11989 AnalyzeNextGame()
11990 {
11991     ReloadGame(1); // next game
11992 }
11993
11994 int
11995 AutoPlayOneMove ()
11996 {
11997     int fromX, fromY, toX, toY;
11998
11999     if (appData.debugMode) {
12000       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12001     }
12002
12003     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12004       return FALSE;
12005
12006     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12007       pvInfoList[currentMove].depth = programStats.depth;
12008       pvInfoList[currentMove].score = programStats.score;
12009       pvInfoList[currentMove].time  = 0;
12010       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12011       else { // append analysis of final position as comment
12012         char buf[MSG_SIZ];
12013         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12014         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12015       }
12016       programStats.depth = 0;
12017     }
12018
12019     if (currentMove >= forwardMostMove) {
12020       if(gameMode == AnalyzeFile) {
12021           if(appData.loadGameIndex == -1) {
12022             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12023           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12024           } else {
12025           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12026         }
12027       }
12028 //      gameMode = EndOfGame;
12029 //      ModeHighlight();
12030
12031       /* [AS] Clear current move marker at the end of a game */
12032       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12033
12034       return FALSE;
12035     }
12036
12037     toX = moveList[currentMove][2] - AAA;
12038     toY = moveList[currentMove][3] - ONE;
12039
12040     if (moveList[currentMove][1] == '@') {
12041         if (appData.highlightLastMove) {
12042             SetHighlights(-1, -1, toX, toY);
12043         }
12044     } else {
12045         int viaX = moveList[currentMove][5] - AAA;
12046         int viaY = moveList[currentMove][6] - ONE;
12047         fromX = moveList[currentMove][0] - AAA;
12048         fromY = moveList[currentMove][1] - ONE;
12049
12050         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12051
12052         if(moveList[currentMove][4] == ';') { // multi-leg
12053             ChessSquare piece = boards[currentMove][viaY][viaX];
12054             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
12055             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
12056             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
12057             boards[currentMove][viaY][viaX] = piece;
12058         } else
12059         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12060
12061         if (appData.highlightLastMove) {
12062             SetHighlights(fromX, fromY, toX, toY);
12063         }
12064     }
12065     DisplayMove(currentMove);
12066     SendMoveToProgram(currentMove++, &first);
12067     DisplayBothClocks();
12068     DrawPosition(FALSE, boards[currentMove]);
12069     // [HGM] PV info: always display, routine tests if empty
12070     DisplayComment(currentMove - 1, commentList[currentMove]);
12071     return TRUE;
12072 }
12073
12074
12075 int
12076 LoadGameOneMove (ChessMove readAhead)
12077 {
12078     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12079     char promoChar = NULLCHAR;
12080     ChessMove moveType;
12081     char move[MSG_SIZ];
12082     char *p, *q;
12083
12084     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12085         gameMode != AnalyzeMode && gameMode != Training) {
12086         gameFileFP = NULL;
12087         return FALSE;
12088     }
12089
12090     yyboardindex = forwardMostMove;
12091     if (readAhead != EndOfFile) {
12092       moveType = readAhead;
12093     } else {
12094       if (gameFileFP == NULL)
12095           return FALSE;
12096       moveType = (ChessMove) Myylex();
12097     }
12098
12099     done = FALSE;
12100     switch (moveType) {
12101       case Comment:
12102         if (appData.debugMode)
12103           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12104         p = yy_text;
12105
12106         /* append the comment but don't display it */
12107         AppendComment(currentMove, p, FALSE);
12108         return TRUE;
12109
12110       case WhiteCapturesEnPassant:
12111       case BlackCapturesEnPassant:
12112       case WhitePromotion:
12113       case BlackPromotion:
12114       case WhiteNonPromotion:
12115       case BlackNonPromotion:
12116       case NormalMove:
12117       case FirstLeg:
12118       case WhiteKingSideCastle:
12119       case WhiteQueenSideCastle:
12120       case BlackKingSideCastle:
12121       case BlackQueenSideCastle:
12122       case WhiteKingSideCastleWild:
12123       case WhiteQueenSideCastleWild:
12124       case BlackKingSideCastleWild:
12125       case BlackQueenSideCastleWild:
12126       /* PUSH Fabien */
12127       case WhiteHSideCastleFR:
12128       case WhiteASideCastleFR:
12129       case BlackHSideCastleFR:
12130       case BlackASideCastleFR:
12131       /* POP Fabien */
12132         if (appData.debugMode)
12133           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12134         fromX = currentMoveString[0] - AAA;
12135         fromY = currentMoveString[1] - ONE;
12136         toX = currentMoveString[2] - AAA;
12137         toY = currentMoveString[3] - ONE;
12138         promoChar = currentMoveString[4];
12139         if(promoChar == ';') promoChar = currentMoveString[7];
12140         break;
12141
12142       case WhiteDrop:
12143       case BlackDrop:
12144         if (appData.debugMode)
12145           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12146         fromX = moveType == WhiteDrop ?
12147           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12148         (int) CharToPiece(ToLower(currentMoveString[0]));
12149         fromY = DROP_RANK;
12150         toX = currentMoveString[2] - AAA;
12151         toY = currentMoveString[3] - ONE;
12152         break;
12153
12154       case WhiteWins:
12155       case BlackWins:
12156       case GameIsDrawn:
12157       case GameUnfinished:
12158         if (appData.debugMode)
12159           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12160         p = strchr(yy_text, '{');
12161         if (p == NULL) p = strchr(yy_text, '(');
12162         if (p == NULL) {
12163             p = yy_text;
12164             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12165         } else {
12166             q = strchr(p, *p == '{' ? '}' : ')');
12167             if (q != NULL) *q = NULLCHAR;
12168             p++;
12169         }
12170         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12171         GameEnds(moveType, p, GE_FILE);
12172         done = TRUE;
12173         if (cmailMsgLoaded) {
12174             ClearHighlights();
12175             flipView = WhiteOnMove(currentMove);
12176             if (moveType == GameUnfinished) flipView = !flipView;
12177             if (appData.debugMode)
12178               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12179         }
12180         break;
12181
12182       case EndOfFile:
12183         if (appData.debugMode)
12184           fprintf(debugFP, "Parser hit end of file\n");
12185         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12186           case MT_NONE:
12187           case MT_CHECK:
12188             break;
12189           case MT_CHECKMATE:
12190           case MT_STAINMATE:
12191             if (WhiteOnMove(currentMove)) {
12192                 GameEnds(BlackWins, "Black mates", GE_FILE);
12193             } else {
12194                 GameEnds(WhiteWins, "White mates", GE_FILE);
12195             }
12196             break;
12197           case MT_STALEMATE:
12198             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12199             break;
12200         }
12201         done = TRUE;
12202         break;
12203
12204       case MoveNumberOne:
12205         if (lastLoadGameStart == GNUChessGame) {
12206             /* GNUChessGames have numbers, but they aren't move numbers */
12207             if (appData.debugMode)
12208               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12209                       yy_text, (int) moveType);
12210             return LoadGameOneMove(EndOfFile); /* tail recursion */
12211         }
12212         /* else fall thru */
12213
12214       case XBoardGame:
12215       case GNUChessGame:
12216       case PGNTag:
12217         /* Reached start of next game in file */
12218         if (appData.debugMode)
12219           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12220         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12221           case MT_NONE:
12222           case MT_CHECK:
12223             break;
12224           case MT_CHECKMATE:
12225           case MT_STAINMATE:
12226             if (WhiteOnMove(currentMove)) {
12227                 GameEnds(BlackWins, "Black mates", GE_FILE);
12228             } else {
12229                 GameEnds(WhiteWins, "White mates", GE_FILE);
12230             }
12231             break;
12232           case MT_STALEMATE:
12233             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12234             break;
12235         }
12236         done = TRUE;
12237         break;
12238
12239       case PositionDiagram:     /* should not happen; ignore */
12240       case ElapsedTime:         /* ignore */
12241       case NAG:                 /* ignore */
12242         if (appData.debugMode)
12243           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12244                   yy_text, (int) moveType);
12245         return LoadGameOneMove(EndOfFile); /* tail recursion */
12246
12247       case IllegalMove:
12248         if (appData.testLegality) {
12249             if (appData.debugMode)
12250               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12251             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12252                     (forwardMostMove / 2) + 1,
12253                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12254             DisplayError(move, 0);
12255             done = TRUE;
12256         } else {
12257             if (appData.debugMode)
12258               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12259                       yy_text, currentMoveString);
12260             if(currentMoveString[1] == '@') {
12261                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12262                 fromY = DROP_RANK;
12263             } else {
12264                 fromX = currentMoveString[0] - AAA;
12265                 fromY = currentMoveString[1] - ONE;
12266             }
12267             toX = currentMoveString[2] - AAA;
12268             toY = currentMoveString[3] - ONE;
12269             promoChar = currentMoveString[4];
12270         }
12271         break;
12272
12273       case AmbiguousMove:
12274         if (appData.debugMode)
12275           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12276         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12277                 (forwardMostMove / 2) + 1,
12278                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12279         DisplayError(move, 0);
12280         done = TRUE;
12281         break;
12282
12283       default:
12284       case ImpossibleMove:
12285         if (appData.debugMode)
12286           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12287         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12288                 (forwardMostMove / 2) + 1,
12289                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12290         DisplayError(move, 0);
12291         done = TRUE;
12292         break;
12293     }
12294
12295     if (done) {
12296         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12297             DrawPosition(FALSE, boards[currentMove]);
12298             DisplayBothClocks();
12299             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12300               DisplayComment(currentMove - 1, commentList[currentMove]);
12301         }
12302         (void) StopLoadGameTimer();
12303         gameFileFP = NULL;
12304         cmailOldMove = forwardMostMove;
12305         return FALSE;
12306     } else {
12307         /* currentMoveString is set as a side-effect of yylex */
12308
12309         thinkOutput[0] = NULLCHAR;
12310         MakeMove(fromX, fromY, toX, toY, promoChar);
12311         killX = killY = -1; // [HGM] lion: used up
12312         currentMove = forwardMostMove;
12313         return TRUE;
12314     }
12315 }
12316
12317 /* Load the nth game from the given file */
12318 int
12319 LoadGameFromFile (char *filename, int n, char *title, int useList)
12320 {
12321     FILE *f;
12322     char buf[MSG_SIZ];
12323
12324     if (strcmp(filename, "-") == 0) {
12325         f = stdin;
12326         title = "stdin";
12327     } else {
12328         f = fopen(filename, "rb");
12329         if (f == NULL) {
12330           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12331             DisplayError(buf, errno);
12332             return FALSE;
12333         }
12334     }
12335     if (fseek(f, 0, 0) == -1) {
12336         /* f is not seekable; probably a pipe */
12337         useList = FALSE;
12338     }
12339     if (useList && n == 0) {
12340         int error = GameListBuild(f);
12341         if (error) {
12342             DisplayError(_("Cannot build game list"), error);
12343         } else if (!ListEmpty(&gameList) &&
12344                    ((ListGame *) gameList.tailPred)->number > 1) {
12345             GameListPopUp(f, title);
12346             return TRUE;
12347         }
12348         GameListDestroy();
12349         n = 1;
12350     }
12351     if (n == 0) n = 1;
12352     return LoadGame(f, n, title, FALSE);
12353 }
12354
12355
12356 void
12357 MakeRegisteredMove ()
12358 {
12359     int fromX, fromY, toX, toY;
12360     char promoChar;
12361     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12362         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12363           case CMAIL_MOVE:
12364           case CMAIL_DRAW:
12365             if (appData.debugMode)
12366               fprintf(debugFP, "Restoring %s for game %d\n",
12367                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12368
12369             thinkOutput[0] = NULLCHAR;
12370             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12371             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12372             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12373             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12374             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12375             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12376             MakeMove(fromX, fromY, toX, toY, promoChar);
12377             ShowMove(fromX, fromY, toX, toY);
12378
12379             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12380               case MT_NONE:
12381               case MT_CHECK:
12382                 break;
12383
12384               case MT_CHECKMATE:
12385               case MT_STAINMATE:
12386                 if (WhiteOnMove(currentMove)) {
12387                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12388                 } else {
12389                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12390                 }
12391                 break;
12392
12393               case MT_STALEMATE:
12394                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12395                 break;
12396             }
12397
12398             break;
12399
12400           case CMAIL_RESIGN:
12401             if (WhiteOnMove(currentMove)) {
12402                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12403             } else {
12404                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12405             }
12406             break;
12407
12408           case CMAIL_ACCEPT:
12409             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12410             break;
12411
12412           default:
12413             break;
12414         }
12415     }
12416
12417     return;
12418 }
12419
12420 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12421 int
12422 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12423 {
12424     int retVal;
12425
12426     if (gameNumber > nCmailGames) {
12427         DisplayError(_("No more games in this message"), 0);
12428         return FALSE;
12429     }
12430     if (f == lastLoadGameFP) {
12431         int offset = gameNumber - lastLoadGameNumber;
12432         if (offset == 0) {
12433             cmailMsg[0] = NULLCHAR;
12434             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12435                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12436                 nCmailMovesRegistered--;
12437             }
12438             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12439             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12440                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12441             }
12442         } else {
12443             if (! RegisterMove()) return FALSE;
12444         }
12445     }
12446
12447     retVal = LoadGame(f, gameNumber, title, useList);
12448
12449     /* Make move registered during previous look at this game, if any */
12450     MakeRegisteredMove();
12451
12452     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12453         commentList[currentMove]
12454           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12455         DisplayComment(currentMove - 1, commentList[currentMove]);
12456     }
12457
12458     return retVal;
12459 }
12460
12461 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12462 int
12463 ReloadGame (int offset)
12464 {
12465     int gameNumber = lastLoadGameNumber + offset;
12466     if (lastLoadGameFP == NULL) {
12467         DisplayError(_("No game has been loaded yet"), 0);
12468         return FALSE;
12469     }
12470     if (gameNumber <= 0) {
12471         DisplayError(_("Can't back up any further"), 0);
12472         return FALSE;
12473     }
12474     if (cmailMsgLoaded) {
12475         return CmailLoadGame(lastLoadGameFP, gameNumber,
12476                              lastLoadGameTitle, lastLoadGameUseList);
12477     } else {
12478         return LoadGame(lastLoadGameFP, gameNumber,
12479                         lastLoadGameTitle, lastLoadGameUseList);
12480     }
12481 }
12482
12483 int keys[EmptySquare+1];
12484
12485 int
12486 PositionMatches (Board b1, Board b2)
12487 {
12488     int r, f, sum=0;
12489     switch(appData.searchMode) {
12490         case 1: return CompareWithRights(b1, b2);
12491         case 2:
12492             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12493                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12494             }
12495             return TRUE;
12496         case 3:
12497             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12498               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12499                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12500             }
12501             return sum==0;
12502         case 4:
12503             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12504                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12505             }
12506             return sum==0;
12507     }
12508     return TRUE;
12509 }
12510
12511 #define Q_PROMO  4
12512 #define Q_EP     3
12513 #define Q_BCASTL 2
12514 #define Q_WCASTL 1
12515
12516 int pieceList[256], quickBoard[256];
12517 ChessSquare pieceType[256] = { EmptySquare };
12518 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12519 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12520 int soughtTotal, turn;
12521 Boolean epOK, flipSearch;
12522
12523 typedef struct {
12524     unsigned char piece, to;
12525 } Move;
12526
12527 #define DSIZE (250000)
12528
12529 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12530 Move *moveDatabase = initialSpace;
12531 unsigned int movePtr, dataSize = DSIZE;
12532
12533 int
12534 MakePieceList (Board board, int *counts)
12535 {
12536     int r, f, n=Q_PROMO, total=0;
12537     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12538     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12539         int sq = f + (r<<4);
12540         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12541             quickBoard[sq] = ++n;
12542             pieceList[n] = sq;
12543             pieceType[n] = board[r][f];
12544             counts[board[r][f]]++;
12545             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12546             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12547             total++;
12548         }
12549     }
12550     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12551     return total;
12552 }
12553
12554 void
12555 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12556 {
12557     int sq = fromX + (fromY<<4);
12558     int piece = quickBoard[sq], rook;
12559     quickBoard[sq] = 0;
12560     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12561     if(piece == pieceList[1] && fromY == toY) {
12562       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12563         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12564         moveDatabase[movePtr++].piece = Q_WCASTL;
12565         quickBoard[sq] = piece;
12566         piece = quickBoard[from]; quickBoard[from] = 0;
12567         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12568       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12569         quickBoard[sq] = 0; // remove Rook
12570         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12571         moveDatabase[movePtr++].piece = Q_WCASTL;
12572         quickBoard[sq] = pieceList[1]; // put King
12573         piece = rook;
12574         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12575       }
12576     } else
12577     if(piece == pieceList[2] && fromY == toY) {
12578       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12579         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12580         moveDatabase[movePtr++].piece = Q_BCASTL;
12581         quickBoard[sq] = piece;
12582         piece = quickBoard[from]; quickBoard[from] = 0;
12583         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12584       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12585         quickBoard[sq] = 0; // remove Rook
12586         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12587         moveDatabase[movePtr++].piece = Q_BCASTL;
12588         quickBoard[sq] = pieceList[2]; // put King
12589         piece = rook;
12590         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12591       }
12592     } else
12593     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12594         quickBoard[(fromY<<4)+toX] = 0;
12595         moveDatabase[movePtr].piece = Q_EP;
12596         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12597         moveDatabase[movePtr].to = sq;
12598     } else
12599     if(promoPiece != pieceType[piece]) {
12600         moveDatabase[movePtr++].piece = Q_PROMO;
12601         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12602     }
12603     moveDatabase[movePtr].piece = piece;
12604     quickBoard[sq] = piece;
12605     movePtr++;
12606 }
12607
12608 int
12609 PackGame (Board board)
12610 {
12611     Move *newSpace = NULL;
12612     moveDatabase[movePtr].piece = 0; // terminate previous game
12613     if(movePtr > dataSize) {
12614         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12615         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12616         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12617         if(newSpace) {
12618             int i;
12619             Move *p = moveDatabase, *q = newSpace;
12620             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12621             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12622             moveDatabase = newSpace;
12623         } else { // calloc failed, we must be out of memory. Too bad...
12624             dataSize = 0; // prevent calloc events for all subsequent games
12625             return 0;     // and signal this one isn't cached
12626         }
12627     }
12628     movePtr++;
12629     MakePieceList(board, counts);
12630     return movePtr;
12631 }
12632
12633 int
12634 QuickCompare (Board board, int *minCounts, int *maxCounts)
12635 {   // compare according to search mode
12636     int r, f;
12637     switch(appData.searchMode)
12638     {
12639       case 1: // exact position match
12640         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12641         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12642             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12643         }
12644         break;
12645       case 2: // can have extra material on empty squares
12646         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12647             if(board[r][f] == EmptySquare) continue;
12648             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12649         }
12650         break;
12651       case 3: // material with exact Pawn structure
12652         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12653             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12654             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12655         } // fall through to material comparison
12656       case 4: // exact material
12657         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12658         break;
12659       case 6: // material range with given imbalance
12660         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12661         // fall through to range comparison
12662       case 5: // material range
12663         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12664     }
12665     return TRUE;
12666 }
12667
12668 int
12669 QuickScan (Board board, Move *move)
12670 {   // reconstruct game,and compare all positions in it
12671     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12672     do {
12673         int piece = move->piece;
12674         int to = move->to, from = pieceList[piece];
12675         if(found < 0) { // if already found just scan to game end for final piece count
12676           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12677            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12678            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12679                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12680             ) {
12681             static int lastCounts[EmptySquare+1];
12682             int i;
12683             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12684             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12685           } else stretch = 0;
12686           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12687           if(found >= 0 && !appData.minPieces) return found;
12688         }
12689         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12690           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12691           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12692             piece = (++move)->piece;
12693             from = pieceList[piece];
12694             counts[pieceType[piece]]--;
12695             pieceType[piece] = (ChessSquare) move->to;
12696             counts[move->to]++;
12697           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12698             counts[pieceType[quickBoard[to]]]--;
12699             quickBoard[to] = 0; total--;
12700             move++;
12701             continue;
12702           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12703             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12704             from  = pieceList[piece]; // so this must be King
12705             quickBoard[from] = 0;
12706             pieceList[piece] = to;
12707             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12708             quickBoard[from] = 0; // rook
12709             quickBoard[to] = piece;
12710             to = move->to; piece = move->piece;
12711             goto aftercastle;
12712           }
12713         }
12714         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12715         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12716         quickBoard[from] = 0;
12717       aftercastle:
12718         quickBoard[to] = piece;
12719         pieceList[piece] = to;
12720         cnt++; turn ^= 3;
12721         move++;
12722     } while(1);
12723 }
12724
12725 void
12726 InitSearch ()
12727 {
12728     int r, f;
12729     flipSearch = FALSE;
12730     CopyBoard(soughtBoard, boards[currentMove]);
12731     soughtTotal = MakePieceList(soughtBoard, maxSought);
12732     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12733     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12734     CopyBoard(reverseBoard, boards[currentMove]);
12735     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12736         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12737         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12738         reverseBoard[r][f] = piece;
12739     }
12740     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12741     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12742     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12743                  || (boards[currentMove][CASTLING][2] == NoRights ||
12744                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12745                  && (boards[currentMove][CASTLING][5] == NoRights ||
12746                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12747       ) {
12748         flipSearch = TRUE;
12749         CopyBoard(flipBoard, soughtBoard);
12750         CopyBoard(rotateBoard, reverseBoard);
12751         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12752             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12753             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12754         }
12755     }
12756     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12757     if(appData.searchMode >= 5) {
12758         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12759         MakePieceList(soughtBoard, minSought);
12760         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12761     }
12762     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12763         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12764 }
12765
12766 GameInfo dummyInfo;
12767 static int creatingBook;
12768
12769 int
12770 GameContainsPosition (FILE *f, ListGame *lg)
12771 {
12772     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12773     int fromX, fromY, toX, toY;
12774     char promoChar;
12775     static int initDone=FALSE;
12776
12777     // weed out games based on numerical tag comparison
12778     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12779     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12780     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12781     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12782     if(!initDone) {
12783         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12784         initDone = TRUE;
12785     }
12786     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12787     else CopyBoard(boards[scratch], initialPosition); // default start position
12788     if(lg->moves) {
12789         turn = btm + 1;
12790         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12791         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12792     }
12793     if(btm) plyNr++;
12794     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12795     fseek(f, lg->offset, 0);
12796     yynewfile(f);
12797     while(1) {
12798         yyboardindex = scratch;
12799         quickFlag = plyNr+1;
12800         next = Myylex();
12801         quickFlag = 0;
12802         switch(next) {
12803             case PGNTag:
12804                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12805             default:
12806                 continue;
12807
12808             case XBoardGame:
12809             case GNUChessGame:
12810                 if(plyNr) return -1; // after we have seen moves, this is for new game
12811               continue;
12812
12813             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12814             case ImpossibleMove:
12815             case WhiteWins: // game ends here with these four
12816             case BlackWins:
12817             case GameIsDrawn:
12818             case GameUnfinished:
12819                 return -1;
12820
12821             case IllegalMove:
12822                 if(appData.testLegality) return -1;
12823             case WhiteCapturesEnPassant:
12824             case BlackCapturesEnPassant:
12825             case WhitePromotion:
12826             case BlackPromotion:
12827             case WhiteNonPromotion:
12828             case BlackNonPromotion:
12829             case NormalMove:
12830             case FirstLeg:
12831             case WhiteKingSideCastle:
12832             case WhiteQueenSideCastle:
12833             case BlackKingSideCastle:
12834             case BlackQueenSideCastle:
12835             case WhiteKingSideCastleWild:
12836             case WhiteQueenSideCastleWild:
12837             case BlackKingSideCastleWild:
12838             case BlackQueenSideCastleWild:
12839             case WhiteHSideCastleFR:
12840             case WhiteASideCastleFR:
12841             case BlackHSideCastleFR:
12842             case BlackASideCastleFR:
12843                 fromX = currentMoveString[0] - AAA;
12844                 fromY = currentMoveString[1] - ONE;
12845                 toX = currentMoveString[2] - AAA;
12846                 toY = currentMoveString[3] - ONE;
12847                 promoChar = currentMoveString[4];
12848                 break;
12849             case WhiteDrop:
12850             case BlackDrop:
12851                 fromX = next == WhiteDrop ?
12852                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12853                   (int) CharToPiece(ToLower(currentMoveString[0]));
12854                 fromY = DROP_RANK;
12855                 toX = currentMoveString[2] - AAA;
12856                 toY = currentMoveString[3] - ONE;
12857                 promoChar = 0;
12858                 break;
12859         }
12860         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12861         plyNr++;
12862         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12863         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12864         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12865         if(appData.findMirror) {
12866             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12867             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12868         }
12869     }
12870 }
12871
12872 /* Load the nth game from open file f */
12873 int
12874 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12875 {
12876     ChessMove cm;
12877     char buf[MSG_SIZ];
12878     int gn = gameNumber;
12879     ListGame *lg = NULL;
12880     int numPGNTags = 0;
12881     int err, pos = -1;
12882     GameMode oldGameMode;
12883     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12884     char oldName[MSG_SIZ];
12885
12886     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12887
12888     if (appData.debugMode)
12889         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12890
12891     if (gameMode == Training )
12892         SetTrainingModeOff();
12893
12894     oldGameMode = gameMode;
12895     if (gameMode != BeginningOfGame) {
12896       Reset(FALSE, TRUE);
12897     }
12898     killX = killY = -1; // [HGM] lion: in case we did not Reset
12899
12900     gameFileFP = f;
12901     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12902         fclose(lastLoadGameFP);
12903     }
12904
12905     if (useList) {
12906         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12907
12908         if (lg) {
12909             fseek(f, lg->offset, 0);
12910             GameListHighlight(gameNumber);
12911             pos = lg->position;
12912             gn = 1;
12913         }
12914         else {
12915             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12916               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12917             else
12918             DisplayError(_("Game number out of range"), 0);
12919             return FALSE;
12920         }
12921     } else {
12922         GameListDestroy();
12923         if (fseek(f, 0, 0) == -1) {
12924             if (f == lastLoadGameFP ?
12925                 gameNumber == lastLoadGameNumber + 1 :
12926                 gameNumber == 1) {
12927                 gn = 1;
12928             } else {
12929                 DisplayError(_("Can't seek on game file"), 0);
12930                 return FALSE;
12931             }
12932         }
12933     }
12934     lastLoadGameFP = f;
12935     lastLoadGameNumber = gameNumber;
12936     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12937     lastLoadGameUseList = useList;
12938
12939     yynewfile(f);
12940
12941     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12942       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12943                 lg->gameInfo.black);
12944             DisplayTitle(buf);
12945     } else if (*title != NULLCHAR) {
12946         if (gameNumber > 1) {
12947           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12948             DisplayTitle(buf);
12949         } else {
12950             DisplayTitle(title);
12951         }
12952     }
12953
12954     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12955         gameMode = PlayFromGameFile;
12956         ModeHighlight();
12957     }
12958
12959     currentMove = forwardMostMove = backwardMostMove = 0;
12960     CopyBoard(boards[0], initialPosition);
12961     StopClocks();
12962
12963     /*
12964      * Skip the first gn-1 games in the file.
12965      * Also skip over anything that precedes an identifiable
12966      * start of game marker, to avoid being confused by
12967      * garbage at the start of the file.  Currently
12968      * recognized start of game markers are the move number "1",
12969      * the pattern "gnuchess .* game", the pattern
12970      * "^[#;%] [^ ]* game file", and a PGN tag block.
12971      * A game that starts with one of the latter two patterns
12972      * will also have a move number 1, possibly
12973      * following a position diagram.
12974      * 5-4-02: Let's try being more lenient and allowing a game to
12975      * start with an unnumbered move.  Does that break anything?
12976      */
12977     cm = lastLoadGameStart = EndOfFile;
12978     while (gn > 0) {
12979         yyboardindex = forwardMostMove;
12980         cm = (ChessMove) Myylex();
12981         switch (cm) {
12982           case EndOfFile:
12983             if (cmailMsgLoaded) {
12984                 nCmailGames = CMAIL_MAX_GAMES - gn;
12985             } else {
12986                 Reset(TRUE, TRUE);
12987                 DisplayError(_("Game not found in file"), 0);
12988             }
12989             return FALSE;
12990
12991           case GNUChessGame:
12992           case XBoardGame:
12993             gn--;
12994             lastLoadGameStart = cm;
12995             break;
12996
12997           case MoveNumberOne:
12998             switch (lastLoadGameStart) {
12999               case GNUChessGame:
13000               case XBoardGame:
13001               case PGNTag:
13002                 break;
13003               case MoveNumberOne:
13004               case EndOfFile:
13005                 gn--;           /* count this game */
13006                 lastLoadGameStart = cm;
13007                 break;
13008               default:
13009                 /* impossible */
13010                 break;
13011             }
13012             break;
13013
13014           case PGNTag:
13015             switch (lastLoadGameStart) {
13016               case GNUChessGame:
13017               case PGNTag:
13018               case MoveNumberOne:
13019               case EndOfFile:
13020                 gn--;           /* count this game */
13021                 lastLoadGameStart = cm;
13022                 break;
13023               case XBoardGame:
13024                 lastLoadGameStart = cm; /* game counted already */
13025                 break;
13026               default:
13027                 /* impossible */
13028                 break;
13029             }
13030             if (gn > 0) {
13031                 do {
13032                     yyboardindex = forwardMostMove;
13033                     cm = (ChessMove) Myylex();
13034                 } while (cm == PGNTag || cm == Comment);
13035             }
13036             break;
13037
13038           case WhiteWins:
13039           case BlackWins:
13040           case GameIsDrawn:
13041             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13042                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13043                     != CMAIL_OLD_RESULT) {
13044                     nCmailResults ++ ;
13045                     cmailResult[  CMAIL_MAX_GAMES
13046                                 - gn - 1] = CMAIL_OLD_RESULT;
13047                 }
13048             }
13049             break;
13050
13051           case NormalMove:
13052           case FirstLeg:
13053             /* Only a NormalMove can be at the start of a game
13054              * without a position diagram. */
13055             if (lastLoadGameStart == EndOfFile ) {
13056               gn--;
13057               lastLoadGameStart = MoveNumberOne;
13058             }
13059             break;
13060
13061           default:
13062             break;
13063         }
13064     }
13065
13066     if (appData.debugMode)
13067       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13068
13069     if (cm == XBoardGame) {
13070         /* Skip any header junk before position diagram and/or move 1 */
13071         for (;;) {
13072             yyboardindex = forwardMostMove;
13073             cm = (ChessMove) Myylex();
13074
13075             if (cm == EndOfFile ||
13076                 cm == GNUChessGame || cm == XBoardGame) {
13077                 /* Empty game; pretend end-of-file and handle later */
13078                 cm = EndOfFile;
13079                 break;
13080             }
13081
13082             if (cm == MoveNumberOne || cm == PositionDiagram ||
13083                 cm == PGNTag || cm == Comment)
13084               break;
13085         }
13086     } else if (cm == GNUChessGame) {
13087         if (gameInfo.event != NULL) {
13088             free(gameInfo.event);
13089         }
13090         gameInfo.event = StrSave(yy_text);
13091     }
13092
13093     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13094     while (cm == PGNTag) {
13095         if (appData.debugMode)
13096           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13097         err = ParsePGNTag(yy_text, &gameInfo);
13098         if (!err) numPGNTags++;
13099
13100         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13101         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13102             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13103             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13104             InitPosition(TRUE);
13105             oldVariant = gameInfo.variant;
13106             if (appData.debugMode)
13107               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13108         }
13109
13110
13111         if (gameInfo.fen != NULL) {
13112           Board initial_position;
13113           startedFromSetupPosition = TRUE;
13114           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13115             Reset(TRUE, TRUE);
13116             DisplayError(_("Bad FEN position in file"), 0);
13117             return FALSE;
13118           }
13119           CopyBoard(boards[0], initial_position);
13120           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13121             CopyBoard(initialPosition, initial_position);
13122           if (blackPlaysFirst) {
13123             currentMove = forwardMostMove = backwardMostMove = 1;
13124             CopyBoard(boards[1], initial_position);
13125             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13126             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13127             timeRemaining[0][1] = whiteTimeRemaining;
13128             timeRemaining[1][1] = blackTimeRemaining;
13129             if (commentList[0] != NULL) {
13130               commentList[1] = commentList[0];
13131               commentList[0] = NULL;
13132             }
13133           } else {
13134             currentMove = forwardMostMove = backwardMostMove = 0;
13135           }
13136           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13137           {   int i;
13138               initialRulePlies = FENrulePlies;
13139               for( i=0; i< nrCastlingRights; i++ )
13140                   initialRights[i] = initial_position[CASTLING][i];
13141           }
13142           yyboardindex = forwardMostMove;
13143           free(gameInfo.fen);
13144           gameInfo.fen = NULL;
13145         }
13146
13147         yyboardindex = forwardMostMove;
13148         cm = (ChessMove) Myylex();
13149
13150         /* Handle comments interspersed among the tags */
13151         while (cm == Comment) {
13152             char *p;
13153             if (appData.debugMode)
13154               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13155             p = yy_text;
13156             AppendComment(currentMove, p, FALSE);
13157             yyboardindex = forwardMostMove;
13158             cm = (ChessMove) Myylex();
13159         }
13160     }
13161
13162     /* don't rely on existence of Event tag since if game was
13163      * pasted from clipboard the Event tag may not exist
13164      */
13165     if (numPGNTags > 0){
13166         char *tags;
13167         if (gameInfo.variant == VariantNormal) {
13168           VariantClass v = StringToVariant(gameInfo.event);
13169           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13170           if(v < VariantShogi) gameInfo.variant = v;
13171         }
13172         if (!matchMode) {
13173           if( appData.autoDisplayTags ) {
13174             tags = PGNTags(&gameInfo);
13175             TagsPopUp(tags, CmailMsg());
13176             free(tags);
13177           }
13178         }
13179     } else {
13180         /* Make something up, but don't display it now */
13181         SetGameInfo();
13182         TagsPopDown();
13183     }
13184
13185     if (cm == PositionDiagram) {
13186         int i, j;
13187         char *p;
13188         Board initial_position;
13189
13190         if (appData.debugMode)
13191           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13192
13193         if (!startedFromSetupPosition) {
13194             p = yy_text;
13195             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13196               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13197                 switch (*p) {
13198                   case '{':
13199                   case '[':
13200                   case '-':
13201                   case ' ':
13202                   case '\t':
13203                   case '\n':
13204                   case '\r':
13205                     break;
13206                   default:
13207                     initial_position[i][j++] = CharToPiece(*p);
13208                     break;
13209                 }
13210             while (*p == ' ' || *p == '\t' ||
13211                    *p == '\n' || *p == '\r') p++;
13212
13213             if (strncmp(p, "black", strlen("black"))==0)
13214               blackPlaysFirst = TRUE;
13215             else
13216               blackPlaysFirst = FALSE;
13217             startedFromSetupPosition = TRUE;
13218
13219             CopyBoard(boards[0], initial_position);
13220             if (blackPlaysFirst) {
13221                 currentMove = forwardMostMove = backwardMostMove = 1;
13222                 CopyBoard(boards[1], initial_position);
13223                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13224                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13225                 timeRemaining[0][1] = whiteTimeRemaining;
13226                 timeRemaining[1][1] = blackTimeRemaining;
13227                 if (commentList[0] != NULL) {
13228                     commentList[1] = commentList[0];
13229                     commentList[0] = NULL;
13230                 }
13231             } else {
13232                 currentMove = forwardMostMove = backwardMostMove = 0;
13233             }
13234         }
13235         yyboardindex = forwardMostMove;
13236         cm = (ChessMove) Myylex();
13237     }
13238
13239   if(!creatingBook) {
13240     if (first.pr == NoProc) {
13241         StartChessProgram(&first);
13242     }
13243     InitChessProgram(&first, FALSE);
13244     if(gameInfo.variant == VariantUnknown && *oldName) {
13245         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13246         gameInfo.variant = v;
13247     }
13248     SendToProgram("force\n", &first);
13249     if (startedFromSetupPosition) {
13250         SendBoard(&first, forwardMostMove);
13251     if (appData.debugMode) {
13252         fprintf(debugFP, "Load Game\n");
13253     }
13254         DisplayBothClocks();
13255     }
13256   }
13257
13258     /* [HGM] server: flag to write setup moves in broadcast file as one */
13259     loadFlag = appData.suppressLoadMoves;
13260
13261     while (cm == Comment) {
13262         char *p;
13263         if (appData.debugMode)
13264           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13265         p = yy_text;
13266         AppendComment(currentMove, p, FALSE);
13267         yyboardindex = forwardMostMove;
13268         cm = (ChessMove) Myylex();
13269     }
13270
13271     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13272         cm == WhiteWins || cm == BlackWins ||
13273         cm == GameIsDrawn || cm == GameUnfinished) {
13274         DisplayMessage("", _("No moves in game"));
13275         if (cmailMsgLoaded) {
13276             if (appData.debugMode)
13277               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13278             ClearHighlights();
13279             flipView = FALSE;
13280         }
13281         DrawPosition(FALSE, boards[currentMove]);
13282         DisplayBothClocks();
13283         gameMode = EditGame;
13284         ModeHighlight();
13285         gameFileFP = NULL;
13286         cmailOldMove = 0;
13287         return TRUE;
13288     }
13289
13290     // [HGM] PV info: routine tests if comment empty
13291     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13292         DisplayComment(currentMove - 1, commentList[currentMove]);
13293     }
13294     if (!matchMode && appData.timeDelay != 0)
13295       DrawPosition(FALSE, boards[currentMove]);
13296
13297     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13298       programStats.ok_to_send = 1;
13299     }
13300
13301     /* if the first token after the PGN tags is a move
13302      * and not move number 1, retrieve it from the parser
13303      */
13304     if (cm != MoveNumberOne)
13305         LoadGameOneMove(cm);
13306
13307     /* load the remaining moves from the file */
13308     while (LoadGameOneMove(EndOfFile)) {
13309       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13310       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13311     }
13312
13313     /* rewind to the start of the game */
13314     currentMove = backwardMostMove;
13315
13316     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13317
13318     if (oldGameMode == AnalyzeFile) {
13319       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13320       AnalyzeFileEvent();
13321     } else
13322     if (oldGameMode == AnalyzeMode) {
13323       AnalyzeFileEvent();
13324     }
13325
13326     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13327         long int w, b; // [HGM] adjourn: restore saved clock times
13328         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13329         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13330             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13331             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13332         }
13333     }
13334
13335     if(creatingBook) return TRUE;
13336     if (!matchMode && pos > 0) {
13337         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13338     } else
13339     if (matchMode || appData.timeDelay == 0) {
13340       ToEndEvent();
13341     } else if (appData.timeDelay > 0) {
13342       AutoPlayGameLoop();
13343     }
13344
13345     if (appData.debugMode)
13346         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13347
13348     loadFlag = 0; /* [HGM] true game starts */
13349     return TRUE;
13350 }
13351
13352 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13353 int
13354 ReloadPosition (int offset)
13355 {
13356     int positionNumber = lastLoadPositionNumber + offset;
13357     if (lastLoadPositionFP == NULL) {
13358         DisplayError(_("No position has been loaded yet"), 0);
13359         return FALSE;
13360     }
13361     if (positionNumber <= 0) {
13362         DisplayError(_("Can't back up any further"), 0);
13363         return FALSE;
13364     }
13365     return LoadPosition(lastLoadPositionFP, positionNumber,
13366                         lastLoadPositionTitle);
13367 }
13368
13369 /* Load the nth position from the given file */
13370 int
13371 LoadPositionFromFile (char *filename, int n, char *title)
13372 {
13373     FILE *f;
13374     char buf[MSG_SIZ];
13375
13376     if (strcmp(filename, "-") == 0) {
13377         return LoadPosition(stdin, n, "stdin");
13378     } else {
13379         f = fopen(filename, "rb");
13380         if (f == NULL) {
13381             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13382             DisplayError(buf, errno);
13383             return FALSE;
13384         } else {
13385             return LoadPosition(f, n, title);
13386         }
13387     }
13388 }
13389
13390 /* Load the nth position from the given open file, and close it */
13391 int
13392 LoadPosition (FILE *f, int positionNumber, char *title)
13393 {
13394     char *p, line[MSG_SIZ];
13395     Board initial_position;
13396     int i, j, fenMode, pn;
13397
13398     if (gameMode == Training )
13399         SetTrainingModeOff();
13400
13401     if (gameMode != BeginningOfGame) {
13402         Reset(FALSE, TRUE);
13403     }
13404     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13405         fclose(lastLoadPositionFP);
13406     }
13407     if (positionNumber == 0) positionNumber = 1;
13408     lastLoadPositionFP = f;
13409     lastLoadPositionNumber = positionNumber;
13410     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13411     if (first.pr == NoProc && !appData.noChessProgram) {
13412       StartChessProgram(&first);
13413       InitChessProgram(&first, FALSE);
13414     }
13415     pn = positionNumber;
13416     if (positionNumber < 0) {
13417         /* Negative position number means to seek to that byte offset */
13418         if (fseek(f, -positionNumber, 0) == -1) {
13419             DisplayError(_("Can't seek on position file"), 0);
13420             return FALSE;
13421         };
13422         pn = 1;
13423     } else {
13424         if (fseek(f, 0, 0) == -1) {
13425             if (f == lastLoadPositionFP ?
13426                 positionNumber == lastLoadPositionNumber + 1 :
13427                 positionNumber == 1) {
13428                 pn = 1;
13429             } else {
13430                 DisplayError(_("Can't seek on position file"), 0);
13431                 return FALSE;
13432             }
13433         }
13434     }
13435     /* See if this file is FEN or old-style xboard */
13436     if (fgets(line, MSG_SIZ, f) == NULL) {
13437         DisplayError(_("Position not found in file"), 0);
13438         return FALSE;
13439     }
13440     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13441     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13442
13443     if (pn >= 2) {
13444         if (fenMode || line[0] == '#') pn--;
13445         while (pn > 0) {
13446             /* skip positions before number pn */
13447             if (fgets(line, MSG_SIZ, f) == NULL) {
13448                 Reset(TRUE, TRUE);
13449                 DisplayError(_("Position not found in file"), 0);
13450                 return FALSE;
13451             }
13452             if (fenMode || line[0] == '#') pn--;
13453         }
13454     }
13455
13456     if (fenMode) {
13457         char *p;
13458         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13459             DisplayError(_("Bad FEN position in file"), 0);
13460             return FALSE;
13461         }
13462         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13463             sscanf(p+3, "%s", bestMove);
13464         } else *bestMove = NULLCHAR;
13465     } else {
13466         (void) fgets(line, MSG_SIZ, f);
13467         (void) fgets(line, MSG_SIZ, f);
13468
13469         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13470             (void) fgets(line, MSG_SIZ, f);
13471             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13472                 if (*p == ' ')
13473                   continue;
13474                 initial_position[i][j++] = CharToPiece(*p);
13475             }
13476         }
13477
13478         blackPlaysFirst = FALSE;
13479         if (!feof(f)) {
13480             (void) fgets(line, MSG_SIZ, f);
13481             if (strncmp(line, "black", strlen("black"))==0)
13482               blackPlaysFirst = TRUE;
13483         }
13484     }
13485     startedFromSetupPosition = TRUE;
13486
13487     CopyBoard(boards[0], initial_position);
13488     if (blackPlaysFirst) {
13489         currentMove = forwardMostMove = backwardMostMove = 1;
13490         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13491         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13492         CopyBoard(boards[1], initial_position);
13493         DisplayMessage("", _("Black to play"));
13494     } else {
13495         currentMove = forwardMostMove = backwardMostMove = 0;
13496         DisplayMessage("", _("White to play"));
13497     }
13498     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13499     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13500         SendToProgram("force\n", &first);
13501         SendBoard(&first, forwardMostMove);
13502     }
13503     if (appData.debugMode) {
13504 int i, j;
13505   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13506   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13507         fprintf(debugFP, "Load Position\n");
13508     }
13509
13510     if (positionNumber > 1) {
13511       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13512         DisplayTitle(line);
13513     } else {
13514         DisplayTitle(title);
13515     }
13516     gameMode = EditGame;
13517     ModeHighlight();
13518     ResetClocks();
13519     timeRemaining[0][1] = whiteTimeRemaining;
13520     timeRemaining[1][1] = blackTimeRemaining;
13521     DrawPosition(FALSE, boards[currentMove]);
13522
13523     return TRUE;
13524 }
13525
13526
13527 void
13528 CopyPlayerNameIntoFileName (char **dest, char *src)
13529 {
13530     while (*src != NULLCHAR && *src != ',') {
13531         if (*src == ' ') {
13532             *(*dest)++ = '_';
13533             src++;
13534         } else {
13535             *(*dest)++ = *src++;
13536         }
13537     }
13538 }
13539
13540 char *
13541 DefaultFileName (char *ext)
13542 {
13543     static char def[MSG_SIZ];
13544     char *p;
13545
13546     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13547         p = def;
13548         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13549         *p++ = '-';
13550         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13551         *p++ = '.';
13552         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13553     } else {
13554         def[0] = NULLCHAR;
13555     }
13556     return def;
13557 }
13558
13559 /* Save the current game to the given file */
13560 int
13561 SaveGameToFile (char *filename, int append)
13562 {
13563     FILE *f;
13564     char buf[MSG_SIZ];
13565     int result, i, t,tot=0;
13566
13567     if (strcmp(filename, "-") == 0) {
13568         return SaveGame(stdout, 0, NULL);
13569     } else {
13570         for(i=0; i<10; i++) { // upto 10 tries
13571              f = fopen(filename, append ? "a" : "w");
13572              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13573              if(f || errno != 13) break;
13574              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13575              tot += t;
13576         }
13577         if (f == NULL) {
13578             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13579             DisplayError(buf, errno);
13580             return FALSE;
13581         } else {
13582             safeStrCpy(buf, lastMsg, MSG_SIZ);
13583             DisplayMessage(_("Waiting for access to save file"), "");
13584             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13585             DisplayMessage(_("Saving game"), "");
13586             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13587             result = SaveGame(f, 0, NULL);
13588             DisplayMessage(buf, "");
13589             return result;
13590         }
13591     }
13592 }
13593
13594 char *
13595 SavePart (char *str)
13596 {
13597     static char buf[MSG_SIZ];
13598     char *p;
13599
13600     p = strchr(str, ' ');
13601     if (p == NULL) return str;
13602     strncpy(buf, str, p - str);
13603     buf[p - str] = NULLCHAR;
13604     return buf;
13605 }
13606
13607 #define PGN_MAX_LINE 75
13608
13609 #define PGN_SIDE_WHITE  0
13610 #define PGN_SIDE_BLACK  1
13611
13612 static int
13613 FindFirstMoveOutOfBook (int side)
13614 {
13615     int result = -1;
13616
13617     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13618         int index = backwardMostMove;
13619         int has_book_hit = 0;
13620
13621         if( (index % 2) != side ) {
13622             index++;
13623         }
13624
13625         while( index < forwardMostMove ) {
13626             /* Check to see if engine is in book */
13627             int depth = pvInfoList[index].depth;
13628             int score = pvInfoList[index].score;
13629             int in_book = 0;
13630
13631             if( depth <= 2 ) {
13632                 in_book = 1;
13633             }
13634             else if( score == 0 && depth == 63 ) {
13635                 in_book = 1; /* Zappa */
13636             }
13637             else if( score == 2 && depth == 99 ) {
13638                 in_book = 1; /* Abrok */
13639             }
13640
13641             has_book_hit += in_book;
13642
13643             if( ! in_book ) {
13644                 result = index;
13645
13646                 break;
13647             }
13648
13649             index += 2;
13650         }
13651     }
13652
13653     return result;
13654 }
13655
13656 void
13657 GetOutOfBookInfo (char * buf)
13658 {
13659     int oob[2];
13660     int i;
13661     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13662
13663     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13664     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13665
13666     *buf = '\0';
13667
13668     if( oob[0] >= 0 || oob[1] >= 0 ) {
13669         for( i=0; i<2; i++ ) {
13670             int idx = oob[i];
13671
13672             if( idx >= 0 ) {
13673                 if( i > 0 && oob[0] >= 0 ) {
13674                     strcat( buf, "   " );
13675                 }
13676
13677                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13678                 sprintf( buf+strlen(buf), "%s%.2f",
13679                     pvInfoList[idx].score >= 0 ? "+" : "",
13680                     pvInfoList[idx].score / 100.0 );
13681             }
13682         }
13683     }
13684 }
13685
13686 /* Save game in PGN style */
13687 static void
13688 SaveGamePGN2 (FILE *f)
13689 {
13690     int i, offset, linelen, newblock;
13691 //    char *movetext;
13692     char numtext[32];
13693     int movelen, numlen, blank;
13694     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13695
13696     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13697
13698     PrintPGNTags(f, &gameInfo);
13699
13700     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13701
13702     if (backwardMostMove > 0 || startedFromSetupPosition) {
13703         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13704         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13705         fprintf(f, "\n{--------------\n");
13706         PrintPosition(f, backwardMostMove);
13707         fprintf(f, "--------------}\n");
13708         free(fen);
13709     }
13710     else {
13711         /* [AS] Out of book annotation */
13712         if( appData.saveOutOfBookInfo ) {
13713             char buf[64];
13714
13715             GetOutOfBookInfo( buf );
13716
13717             if( buf[0] != '\0' ) {
13718                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13719             }
13720         }
13721
13722         fprintf(f, "\n");
13723     }
13724
13725     i = backwardMostMove;
13726     linelen = 0;
13727     newblock = TRUE;
13728
13729     while (i < forwardMostMove) {
13730         /* Print comments preceding this move */
13731         if (commentList[i] != NULL) {
13732             if (linelen > 0) fprintf(f, "\n");
13733             fprintf(f, "%s", commentList[i]);
13734             linelen = 0;
13735             newblock = TRUE;
13736         }
13737
13738         /* Format move number */
13739         if ((i % 2) == 0)
13740           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13741         else
13742           if (newblock)
13743             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13744           else
13745             numtext[0] = NULLCHAR;
13746
13747         numlen = strlen(numtext);
13748         newblock = FALSE;
13749
13750         /* Print move number */
13751         blank = linelen > 0 && numlen > 0;
13752         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13753             fprintf(f, "\n");
13754             linelen = 0;
13755             blank = 0;
13756         }
13757         if (blank) {
13758             fprintf(f, " ");
13759             linelen++;
13760         }
13761         fprintf(f, "%s", numtext);
13762         linelen += numlen;
13763
13764         /* Get move */
13765         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13766         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13767
13768         /* Print move */
13769         blank = linelen > 0 && movelen > 0;
13770         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13771             fprintf(f, "\n");
13772             linelen = 0;
13773             blank = 0;
13774         }
13775         if (blank) {
13776             fprintf(f, " ");
13777             linelen++;
13778         }
13779         fprintf(f, "%s", move_buffer);
13780         linelen += movelen;
13781
13782         /* [AS] Add PV info if present */
13783         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13784             /* [HGM] add time */
13785             char buf[MSG_SIZ]; int seconds;
13786
13787             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13788
13789             if( seconds <= 0)
13790               buf[0] = 0;
13791             else
13792               if( seconds < 30 )
13793                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13794               else
13795                 {
13796                   seconds = (seconds + 4)/10; // round to full seconds
13797                   if( seconds < 60 )
13798                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13799                   else
13800                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13801                 }
13802
13803             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13804                       pvInfoList[i].score >= 0 ? "+" : "",
13805                       pvInfoList[i].score / 100.0,
13806                       pvInfoList[i].depth,
13807                       buf );
13808
13809             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13810
13811             /* Print score/depth */
13812             blank = linelen > 0 && movelen > 0;
13813             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13814                 fprintf(f, "\n");
13815                 linelen = 0;
13816                 blank = 0;
13817             }
13818             if (blank) {
13819                 fprintf(f, " ");
13820                 linelen++;
13821             }
13822             fprintf(f, "%s", move_buffer);
13823             linelen += movelen;
13824         }
13825
13826         i++;
13827     }
13828
13829     /* Start a new line */
13830     if (linelen > 0) fprintf(f, "\n");
13831
13832     /* Print comments after last move */
13833     if (commentList[i] != NULL) {
13834         fprintf(f, "%s\n", commentList[i]);
13835     }
13836
13837     /* Print result */
13838     if (gameInfo.resultDetails != NULL &&
13839         gameInfo.resultDetails[0] != NULLCHAR) {
13840         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13841         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13842            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13843             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13844         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13845     } else {
13846         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13847     }
13848 }
13849
13850 /* Save game in PGN style and close the file */
13851 int
13852 SaveGamePGN (FILE *f)
13853 {
13854     SaveGamePGN2(f);
13855     fclose(f);
13856     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13857     return TRUE;
13858 }
13859
13860 /* Save game in old style and close the file */
13861 int
13862 SaveGameOldStyle (FILE *f)
13863 {
13864     int i, offset;
13865     time_t tm;
13866
13867     tm = time((time_t *) NULL);
13868
13869     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13870     PrintOpponents(f);
13871
13872     if (backwardMostMove > 0 || startedFromSetupPosition) {
13873         fprintf(f, "\n[--------------\n");
13874         PrintPosition(f, backwardMostMove);
13875         fprintf(f, "--------------]\n");
13876     } else {
13877         fprintf(f, "\n");
13878     }
13879
13880     i = backwardMostMove;
13881     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13882
13883     while (i < forwardMostMove) {
13884         if (commentList[i] != NULL) {
13885             fprintf(f, "[%s]\n", commentList[i]);
13886         }
13887
13888         if ((i % 2) == 1) {
13889             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13890             i++;
13891         } else {
13892             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13893             i++;
13894             if (commentList[i] != NULL) {
13895                 fprintf(f, "\n");
13896                 continue;
13897             }
13898             if (i >= forwardMostMove) {
13899                 fprintf(f, "\n");
13900                 break;
13901             }
13902             fprintf(f, "%s\n", parseList[i]);
13903             i++;
13904         }
13905     }
13906
13907     if (commentList[i] != NULL) {
13908         fprintf(f, "[%s]\n", commentList[i]);
13909     }
13910
13911     /* This isn't really the old style, but it's close enough */
13912     if (gameInfo.resultDetails != NULL &&
13913         gameInfo.resultDetails[0] != NULLCHAR) {
13914         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13915                 gameInfo.resultDetails);
13916     } else {
13917         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13918     }
13919
13920     fclose(f);
13921     return TRUE;
13922 }
13923
13924 /* Save the current game to open file f and close the file */
13925 int
13926 SaveGame (FILE *f, int dummy, char *dummy2)
13927 {
13928     if (gameMode == EditPosition) EditPositionDone(TRUE);
13929     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13930     if (appData.oldSaveStyle)
13931       return SaveGameOldStyle(f);
13932     else
13933       return SaveGamePGN(f);
13934 }
13935
13936 /* Save the current position to the given file */
13937 int
13938 SavePositionToFile (char *filename)
13939 {
13940     FILE *f;
13941     char buf[MSG_SIZ];
13942
13943     if (strcmp(filename, "-") == 0) {
13944         return SavePosition(stdout, 0, NULL);
13945     } else {
13946         f = fopen(filename, "a");
13947         if (f == NULL) {
13948             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13949             DisplayError(buf, errno);
13950             return FALSE;
13951         } else {
13952             safeStrCpy(buf, lastMsg, MSG_SIZ);
13953             DisplayMessage(_("Waiting for access to save file"), "");
13954             flock(fileno(f), LOCK_EX); // [HGM] lock
13955             DisplayMessage(_("Saving position"), "");
13956             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13957             SavePosition(f, 0, NULL);
13958             DisplayMessage(buf, "");
13959             return TRUE;
13960         }
13961     }
13962 }
13963
13964 /* Save the current position to the given open file and close the file */
13965 int
13966 SavePosition (FILE *f, int dummy, char *dummy2)
13967 {
13968     time_t tm;
13969     char *fen;
13970
13971     if (gameMode == EditPosition) EditPositionDone(TRUE);
13972     if (appData.oldSaveStyle) {
13973         tm = time((time_t *) NULL);
13974
13975         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13976         PrintOpponents(f);
13977         fprintf(f, "[--------------\n");
13978         PrintPosition(f, currentMove);
13979         fprintf(f, "--------------]\n");
13980     } else {
13981         fen = PositionToFEN(currentMove, NULL, 1);
13982         fprintf(f, "%s\n", fen);
13983         free(fen);
13984     }
13985     fclose(f);
13986     return TRUE;
13987 }
13988
13989 void
13990 ReloadCmailMsgEvent (int unregister)
13991 {
13992 #if !WIN32
13993     static char *inFilename = NULL;
13994     static char *outFilename;
13995     int i;
13996     struct stat inbuf, outbuf;
13997     int status;
13998
13999     /* Any registered moves are unregistered if unregister is set, */
14000     /* i.e. invoked by the signal handler */
14001     if (unregister) {
14002         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14003             cmailMoveRegistered[i] = FALSE;
14004             if (cmailCommentList[i] != NULL) {
14005                 free(cmailCommentList[i]);
14006                 cmailCommentList[i] = NULL;
14007             }
14008         }
14009         nCmailMovesRegistered = 0;
14010     }
14011
14012     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14013         cmailResult[i] = CMAIL_NOT_RESULT;
14014     }
14015     nCmailResults = 0;
14016
14017     if (inFilename == NULL) {
14018         /* Because the filenames are static they only get malloced once  */
14019         /* and they never get freed                                      */
14020         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14021         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14022
14023         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14024         sprintf(outFilename, "%s.out", appData.cmailGameName);
14025     }
14026
14027     status = stat(outFilename, &outbuf);
14028     if (status < 0) {
14029         cmailMailedMove = FALSE;
14030     } else {
14031         status = stat(inFilename, &inbuf);
14032         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14033     }
14034
14035     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14036        counts the games, notes how each one terminated, etc.
14037
14038        It would be nice to remove this kludge and instead gather all
14039        the information while building the game list.  (And to keep it
14040        in the game list nodes instead of having a bunch of fixed-size
14041        parallel arrays.)  Note this will require getting each game's
14042        termination from the PGN tags, as the game list builder does
14043        not process the game moves.  --mann
14044        */
14045     cmailMsgLoaded = TRUE;
14046     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14047
14048     /* Load first game in the file or popup game menu */
14049     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14050
14051 #endif /* !WIN32 */
14052     return;
14053 }
14054
14055 int
14056 RegisterMove ()
14057 {
14058     FILE *f;
14059     char string[MSG_SIZ];
14060
14061     if (   cmailMailedMove
14062         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14063         return TRUE;            /* Allow free viewing  */
14064     }
14065
14066     /* Unregister move to ensure that we don't leave RegisterMove        */
14067     /* with the move registered when the conditions for registering no   */
14068     /* longer hold                                                       */
14069     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14070         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14071         nCmailMovesRegistered --;
14072
14073         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14074           {
14075               free(cmailCommentList[lastLoadGameNumber - 1]);
14076               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14077           }
14078     }
14079
14080     if (cmailOldMove == -1) {
14081         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14082         return FALSE;
14083     }
14084
14085     if (currentMove > cmailOldMove + 1) {
14086         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14087         return FALSE;
14088     }
14089
14090     if (currentMove < cmailOldMove) {
14091         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14092         return FALSE;
14093     }
14094
14095     if (forwardMostMove > currentMove) {
14096         /* Silently truncate extra moves */
14097         TruncateGame();
14098     }
14099
14100     if (   (currentMove == cmailOldMove + 1)
14101         || (   (currentMove == cmailOldMove)
14102             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14103                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14104         if (gameInfo.result != GameUnfinished) {
14105             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14106         }
14107
14108         if (commentList[currentMove] != NULL) {
14109             cmailCommentList[lastLoadGameNumber - 1]
14110               = StrSave(commentList[currentMove]);
14111         }
14112         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14113
14114         if (appData.debugMode)
14115           fprintf(debugFP, "Saving %s for game %d\n",
14116                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14117
14118         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14119
14120         f = fopen(string, "w");
14121         if (appData.oldSaveStyle) {
14122             SaveGameOldStyle(f); /* also closes the file */
14123
14124             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14125             f = fopen(string, "w");
14126             SavePosition(f, 0, NULL); /* also closes the file */
14127         } else {
14128             fprintf(f, "{--------------\n");
14129             PrintPosition(f, currentMove);
14130             fprintf(f, "--------------}\n\n");
14131
14132             SaveGame(f, 0, NULL); /* also closes the file*/
14133         }
14134
14135         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14136         nCmailMovesRegistered ++;
14137     } else if (nCmailGames == 1) {
14138         DisplayError(_("You have not made a move yet"), 0);
14139         return FALSE;
14140     }
14141
14142     return TRUE;
14143 }
14144
14145 void
14146 MailMoveEvent ()
14147 {
14148 #if !WIN32
14149     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14150     FILE *commandOutput;
14151     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14152     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14153     int nBuffers;
14154     int i;
14155     int archived;
14156     char *arcDir;
14157
14158     if (! cmailMsgLoaded) {
14159         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14160         return;
14161     }
14162
14163     if (nCmailGames == nCmailResults) {
14164         DisplayError(_("No unfinished games"), 0);
14165         return;
14166     }
14167
14168 #if CMAIL_PROHIBIT_REMAIL
14169     if (cmailMailedMove) {
14170       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);
14171         DisplayError(msg, 0);
14172         return;
14173     }
14174 #endif
14175
14176     if (! (cmailMailedMove || RegisterMove())) return;
14177
14178     if (   cmailMailedMove
14179         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14180       snprintf(string, MSG_SIZ, partCommandString,
14181                appData.debugMode ? " -v" : "", appData.cmailGameName);
14182         commandOutput = popen(string, "r");
14183
14184         if (commandOutput == NULL) {
14185             DisplayError(_("Failed to invoke cmail"), 0);
14186         } else {
14187             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14188                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14189             }
14190             if (nBuffers > 1) {
14191                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14192                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14193                 nBytes = MSG_SIZ - 1;
14194             } else {
14195                 (void) memcpy(msg, buffer, nBytes);
14196             }
14197             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14198
14199             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14200                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14201
14202                 archived = TRUE;
14203                 for (i = 0; i < nCmailGames; i ++) {
14204                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14205                         archived = FALSE;
14206                     }
14207                 }
14208                 if (   archived
14209                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14210                         != NULL)) {
14211                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14212                            arcDir,
14213                            appData.cmailGameName,
14214                            gameInfo.date);
14215                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14216                     cmailMsgLoaded = FALSE;
14217                 }
14218             }
14219
14220             DisplayInformation(msg);
14221             pclose(commandOutput);
14222         }
14223     } else {
14224         if ((*cmailMsg) != '\0') {
14225             DisplayInformation(cmailMsg);
14226         }
14227     }
14228
14229     return;
14230 #endif /* !WIN32 */
14231 }
14232
14233 char *
14234 CmailMsg ()
14235 {
14236 #if WIN32
14237     return NULL;
14238 #else
14239     int  prependComma = 0;
14240     char number[5];
14241     char string[MSG_SIZ];       /* Space for game-list */
14242     int  i;
14243
14244     if (!cmailMsgLoaded) return "";
14245
14246     if (cmailMailedMove) {
14247       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14248     } else {
14249         /* Create a list of games left */
14250       snprintf(string, MSG_SIZ, "[");
14251         for (i = 0; i < nCmailGames; i ++) {
14252             if (! (   cmailMoveRegistered[i]
14253                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14254                 if (prependComma) {
14255                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14256                 } else {
14257                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14258                     prependComma = 1;
14259                 }
14260
14261                 strcat(string, number);
14262             }
14263         }
14264         strcat(string, "]");
14265
14266         if (nCmailMovesRegistered + nCmailResults == 0) {
14267             switch (nCmailGames) {
14268               case 1:
14269                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14270                 break;
14271
14272               case 2:
14273                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14274                 break;
14275
14276               default:
14277                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14278                          nCmailGames);
14279                 break;
14280             }
14281         } else {
14282             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14283               case 1:
14284                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14285                          string);
14286                 break;
14287
14288               case 0:
14289                 if (nCmailResults == nCmailGames) {
14290                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14291                 } else {
14292                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14293                 }
14294                 break;
14295
14296               default:
14297                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14298                          string);
14299             }
14300         }
14301     }
14302     return cmailMsg;
14303 #endif /* WIN32 */
14304 }
14305
14306 void
14307 ResetGameEvent ()
14308 {
14309     if (gameMode == Training)
14310       SetTrainingModeOff();
14311
14312     Reset(TRUE, TRUE);
14313     cmailMsgLoaded = FALSE;
14314     if (appData.icsActive) {
14315       SendToICS(ics_prefix);
14316       SendToICS("refresh\n");
14317     }
14318 }
14319
14320 void
14321 ExitEvent (int status)
14322 {
14323     exiting++;
14324     if (exiting > 2) {
14325       /* Give up on clean exit */
14326       exit(status);
14327     }
14328     if (exiting > 1) {
14329       /* Keep trying for clean exit */
14330       return;
14331     }
14332
14333     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14334     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14335
14336     if (telnetISR != NULL) {
14337       RemoveInputSource(telnetISR);
14338     }
14339     if (icsPR != NoProc) {
14340       DestroyChildProcess(icsPR, TRUE);
14341     }
14342
14343     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14344     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14345
14346     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14347     /* make sure this other one finishes before killing it!                  */
14348     if(endingGame) { int count = 0;
14349         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14350         while(endingGame && count++ < 10) DoSleep(1);
14351         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14352     }
14353
14354     /* Kill off chess programs */
14355     if (first.pr != NoProc) {
14356         ExitAnalyzeMode();
14357
14358         DoSleep( appData.delayBeforeQuit );
14359         SendToProgram("quit\n", &first);
14360         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14361     }
14362     if (second.pr != NoProc) {
14363         DoSleep( appData.delayBeforeQuit );
14364         SendToProgram("quit\n", &second);
14365         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14366     }
14367     if (first.isr != NULL) {
14368         RemoveInputSource(first.isr);
14369     }
14370     if (second.isr != NULL) {
14371         RemoveInputSource(second.isr);
14372     }
14373
14374     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14375     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14376
14377     ShutDownFrontEnd();
14378     exit(status);
14379 }
14380
14381 void
14382 PauseEngine (ChessProgramState *cps)
14383 {
14384     SendToProgram("pause\n", cps);
14385     cps->pause = 2;
14386 }
14387
14388 void
14389 UnPauseEngine (ChessProgramState *cps)
14390 {
14391     SendToProgram("resume\n", cps);
14392     cps->pause = 1;
14393 }
14394
14395 void
14396 PauseEvent ()
14397 {
14398     if (appData.debugMode)
14399         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14400     if (pausing) {
14401         pausing = FALSE;
14402         ModeHighlight();
14403         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14404             StartClocks();
14405             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14406                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14407                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14408             }
14409             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14410             HandleMachineMove(stashedInputMove, stalledEngine);
14411             stalledEngine = NULL;
14412             return;
14413         }
14414         if (gameMode == MachinePlaysWhite ||
14415             gameMode == TwoMachinesPlay   ||
14416             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14417             if(first.pause)  UnPauseEngine(&first);
14418             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14419             if(second.pause) UnPauseEngine(&second);
14420             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14421             StartClocks();
14422         } else {
14423             DisplayBothClocks();
14424         }
14425         if (gameMode == PlayFromGameFile) {
14426             if (appData.timeDelay >= 0)
14427                 AutoPlayGameLoop();
14428         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14429             Reset(FALSE, TRUE);
14430             SendToICS(ics_prefix);
14431             SendToICS("refresh\n");
14432         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14433             ForwardInner(forwardMostMove);
14434         }
14435         pauseExamInvalid = FALSE;
14436     } else {
14437         switch (gameMode) {
14438           default:
14439             return;
14440           case IcsExamining:
14441             pauseExamForwardMostMove = forwardMostMove;
14442             pauseExamInvalid = FALSE;
14443             /* fall through */
14444           case IcsObserving:
14445           case IcsPlayingWhite:
14446           case IcsPlayingBlack:
14447             pausing = TRUE;
14448             ModeHighlight();
14449             return;
14450           case PlayFromGameFile:
14451             (void) StopLoadGameTimer();
14452             pausing = TRUE;
14453             ModeHighlight();
14454             break;
14455           case BeginningOfGame:
14456             if (appData.icsActive) return;
14457             /* else fall through */
14458           case MachinePlaysWhite:
14459           case MachinePlaysBlack:
14460           case TwoMachinesPlay:
14461             if (forwardMostMove == 0)
14462               return;           /* don't pause if no one has moved */
14463             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14464                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14465                 if(onMove->pause) {           // thinking engine can be paused
14466                     PauseEngine(onMove);      // do it
14467                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14468                         PauseEngine(onMove->other);
14469                     else
14470                         SendToProgram("easy\n", onMove->other);
14471                     StopClocks();
14472                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14473             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14474                 if(first.pause) {
14475                     PauseEngine(&first);
14476                     StopClocks();
14477                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14478             } else { // human on move, pause pondering by either method
14479                 if(first.pause)
14480                     PauseEngine(&first);
14481                 else if(appData.ponderNextMove)
14482                     SendToProgram("easy\n", &first);
14483                 StopClocks();
14484             }
14485             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14486           case AnalyzeMode:
14487             pausing = TRUE;
14488             ModeHighlight();
14489             break;
14490         }
14491     }
14492 }
14493
14494 void
14495 EditCommentEvent ()
14496 {
14497     char title[MSG_SIZ];
14498
14499     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14500       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14501     } else {
14502       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14503                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14504                parseList[currentMove - 1]);
14505     }
14506
14507     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14508 }
14509
14510
14511 void
14512 EditTagsEvent ()
14513 {
14514     char *tags = PGNTags(&gameInfo);
14515     bookUp = FALSE;
14516     EditTagsPopUp(tags, NULL);
14517     free(tags);
14518 }
14519
14520 void
14521 ToggleSecond ()
14522 {
14523   if(second.analyzing) {
14524     SendToProgram("exit\n", &second);
14525     second.analyzing = FALSE;
14526   } else {
14527     if (second.pr == NoProc) StartChessProgram(&second);
14528     InitChessProgram(&second, FALSE);
14529     FeedMovesToProgram(&second, currentMove);
14530
14531     SendToProgram("analyze\n", &second);
14532     second.analyzing = TRUE;
14533   }
14534 }
14535
14536 /* Toggle ShowThinking */
14537 void
14538 ToggleShowThinking()
14539 {
14540   appData.showThinking = !appData.showThinking;
14541   ShowThinkingEvent();
14542 }
14543
14544 int
14545 AnalyzeModeEvent ()
14546 {
14547     char buf[MSG_SIZ];
14548
14549     if (!first.analysisSupport) {
14550       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14551       DisplayError(buf, 0);
14552       return 0;
14553     }
14554     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14555     if (appData.icsActive) {
14556         if (gameMode != IcsObserving) {
14557           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14558             DisplayError(buf, 0);
14559             /* secure check */
14560             if (appData.icsEngineAnalyze) {
14561                 if (appData.debugMode)
14562                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14563                 ExitAnalyzeMode();
14564                 ModeHighlight();
14565             }
14566             return 0;
14567         }
14568         /* if enable, user wants to disable icsEngineAnalyze */
14569         if (appData.icsEngineAnalyze) {
14570                 ExitAnalyzeMode();
14571                 ModeHighlight();
14572                 return 0;
14573         }
14574         appData.icsEngineAnalyze = TRUE;
14575         if (appData.debugMode)
14576             fprintf(debugFP, "ICS engine analyze starting... \n");
14577     }
14578
14579     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14580     if (appData.noChessProgram || gameMode == AnalyzeMode)
14581       return 0;
14582
14583     if (gameMode != AnalyzeFile) {
14584         if (!appData.icsEngineAnalyze) {
14585                EditGameEvent();
14586                if (gameMode != EditGame) return 0;
14587         }
14588         if (!appData.showThinking) ToggleShowThinking();
14589         ResurrectChessProgram();
14590         SendToProgram("analyze\n", &first);
14591         first.analyzing = TRUE;
14592         /*first.maybeThinking = TRUE;*/
14593         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14594         EngineOutputPopUp();
14595     }
14596     if (!appData.icsEngineAnalyze) {
14597         gameMode = AnalyzeMode;
14598         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14599     }
14600     pausing = FALSE;
14601     ModeHighlight();
14602     SetGameInfo();
14603
14604     StartAnalysisClock();
14605     GetTimeMark(&lastNodeCountTime);
14606     lastNodeCount = 0;
14607     return 1;
14608 }
14609
14610 void
14611 AnalyzeFileEvent ()
14612 {
14613     if (appData.noChessProgram || gameMode == AnalyzeFile)
14614       return;
14615
14616     if (!first.analysisSupport) {
14617       char buf[MSG_SIZ];
14618       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14619       DisplayError(buf, 0);
14620       return;
14621     }
14622
14623     if (gameMode != AnalyzeMode) {
14624         keepInfo = 1; // mere annotating should not alter PGN tags
14625         EditGameEvent();
14626         keepInfo = 0;
14627         if (gameMode != EditGame) return;
14628         if (!appData.showThinking) ToggleShowThinking();
14629         ResurrectChessProgram();
14630         SendToProgram("analyze\n", &first);
14631         first.analyzing = TRUE;
14632         /*first.maybeThinking = TRUE;*/
14633         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14634         EngineOutputPopUp();
14635     }
14636     gameMode = AnalyzeFile;
14637     pausing = FALSE;
14638     ModeHighlight();
14639
14640     StartAnalysisClock();
14641     GetTimeMark(&lastNodeCountTime);
14642     lastNodeCount = 0;
14643     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14644     AnalysisPeriodicEvent(1);
14645 }
14646
14647 void
14648 MachineWhiteEvent ()
14649 {
14650     char buf[MSG_SIZ];
14651     char *bookHit = NULL;
14652
14653     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14654       return;
14655
14656
14657     if (gameMode == PlayFromGameFile ||
14658         gameMode == TwoMachinesPlay  ||
14659         gameMode == Training         ||
14660         gameMode == AnalyzeMode      ||
14661         gameMode == EndOfGame)
14662         EditGameEvent();
14663
14664     if (gameMode == EditPosition)
14665         EditPositionDone(TRUE);
14666
14667     if (!WhiteOnMove(currentMove)) {
14668         DisplayError(_("It is not White's turn"), 0);
14669         return;
14670     }
14671
14672     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14673       ExitAnalyzeMode();
14674
14675     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14676         gameMode == AnalyzeFile)
14677         TruncateGame();
14678
14679     ResurrectChessProgram();    /* in case it isn't running */
14680     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14681         gameMode = MachinePlaysWhite;
14682         ResetClocks();
14683     } else
14684     gameMode = MachinePlaysWhite;
14685     pausing = FALSE;
14686     ModeHighlight();
14687     SetGameInfo();
14688     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14689     DisplayTitle(buf);
14690     if (first.sendName) {
14691       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14692       SendToProgram(buf, &first);
14693     }
14694     if (first.sendTime) {
14695       if (first.useColors) {
14696         SendToProgram("black\n", &first); /*gnu kludge*/
14697       }
14698       SendTimeRemaining(&first, TRUE);
14699     }
14700     if (first.useColors) {
14701       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14702     }
14703     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14704     SetMachineThinkingEnables();
14705     first.maybeThinking = TRUE;
14706     StartClocks();
14707     firstMove = FALSE;
14708
14709     if (appData.autoFlipView && !flipView) {
14710       flipView = !flipView;
14711       DrawPosition(FALSE, NULL);
14712       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14713     }
14714
14715     if(bookHit) { // [HGM] book: simulate book reply
14716         static char bookMove[MSG_SIZ]; // a bit generous?
14717
14718         programStats.nodes = programStats.depth = programStats.time =
14719         programStats.score = programStats.got_only_move = 0;
14720         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14721
14722         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14723         strcat(bookMove, bookHit);
14724         HandleMachineMove(bookMove, &first);
14725     }
14726 }
14727
14728 void
14729 MachineBlackEvent ()
14730 {
14731   char buf[MSG_SIZ];
14732   char *bookHit = NULL;
14733
14734     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14735         return;
14736
14737
14738     if (gameMode == PlayFromGameFile ||
14739         gameMode == TwoMachinesPlay  ||
14740         gameMode == Training         ||
14741         gameMode == AnalyzeMode      ||
14742         gameMode == EndOfGame)
14743         EditGameEvent();
14744
14745     if (gameMode == EditPosition)
14746         EditPositionDone(TRUE);
14747
14748     if (WhiteOnMove(currentMove)) {
14749         DisplayError(_("It is not Black's turn"), 0);
14750         return;
14751     }
14752
14753     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14754       ExitAnalyzeMode();
14755
14756     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14757         gameMode == AnalyzeFile)
14758         TruncateGame();
14759
14760     ResurrectChessProgram();    /* in case it isn't running */
14761     gameMode = MachinePlaysBlack;
14762     pausing = FALSE;
14763     ModeHighlight();
14764     SetGameInfo();
14765     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14766     DisplayTitle(buf);
14767     if (first.sendName) {
14768       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14769       SendToProgram(buf, &first);
14770     }
14771     if (first.sendTime) {
14772       if (first.useColors) {
14773         SendToProgram("white\n", &first); /*gnu kludge*/
14774       }
14775       SendTimeRemaining(&first, FALSE);
14776     }
14777     if (first.useColors) {
14778       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14779     }
14780     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14781     SetMachineThinkingEnables();
14782     first.maybeThinking = TRUE;
14783     StartClocks();
14784
14785     if (appData.autoFlipView && flipView) {
14786       flipView = !flipView;
14787       DrawPosition(FALSE, NULL);
14788       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14789     }
14790     if(bookHit) { // [HGM] book: simulate book reply
14791         static char bookMove[MSG_SIZ]; // a bit generous?
14792
14793         programStats.nodes = programStats.depth = programStats.time =
14794         programStats.score = programStats.got_only_move = 0;
14795         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14796
14797         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14798         strcat(bookMove, bookHit);
14799         HandleMachineMove(bookMove, &first);
14800     }
14801 }
14802
14803
14804 void
14805 DisplayTwoMachinesTitle ()
14806 {
14807     char buf[MSG_SIZ];
14808     if (appData.matchGames > 0) {
14809         if(appData.tourneyFile[0]) {
14810           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14811                    gameInfo.white, _("vs."), gameInfo.black,
14812                    nextGame+1, appData.matchGames+1,
14813                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14814         } else
14815         if (first.twoMachinesColor[0] == 'w') {
14816           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14817                    gameInfo.white, _("vs."),  gameInfo.black,
14818                    first.matchWins, second.matchWins,
14819                    matchGame - 1 - (first.matchWins + second.matchWins));
14820         } else {
14821           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14822                    gameInfo.white, _("vs."), gameInfo.black,
14823                    second.matchWins, first.matchWins,
14824                    matchGame - 1 - (first.matchWins + second.matchWins));
14825         }
14826     } else {
14827       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14828     }
14829     DisplayTitle(buf);
14830 }
14831
14832 void
14833 SettingsMenuIfReady ()
14834 {
14835   if (second.lastPing != second.lastPong) {
14836     DisplayMessage("", _("Waiting for second chess program"));
14837     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14838     return;
14839   }
14840   ThawUI();
14841   DisplayMessage("", "");
14842   SettingsPopUp(&second);
14843 }
14844
14845 int
14846 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14847 {
14848     char buf[MSG_SIZ];
14849     if (cps->pr == NoProc) {
14850         StartChessProgram(cps);
14851         if (cps->protocolVersion == 1) {
14852           retry();
14853           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14854         } else {
14855           /* kludge: allow timeout for initial "feature" command */
14856           if(retry != TwoMachinesEventIfReady) FreezeUI();
14857           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14858           DisplayMessage("", buf);
14859           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14860         }
14861         return 1;
14862     }
14863     return 0;
14864 }
14865
14866 void
14867 TwoMachinesEvent P((void))
14868 {
14869     int i;
14870     char buf[MSG_SIZ];
14871     ChessProgramState *onmove;
14872     char *bookHit = NULL;
14873     static int stalling = 0;
14874     TimeMark now;
14875     long wait;
14876
14877     if (appData.noChessProgram) return;
14878
14879     switch (gameMode) {
14880       case TwoMachinesPlay:
14881         return;
14882       case MachinePlaysWhite:
14883       case MachinePlaysBlack:
14884         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14885             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14886             return;
14887         }
14888         /* fall through */
14889       case BeginningOfGame:
14890       case PlayFromGameFile:
14891       case EndOfGame:
14892         EditGameEvent();
14893         if (gameMode != EditGame) return;
14894         break;
14895       case EditPosition:
14896         EditPositionDone(TRUE);
14897         break;
14898       case AnalyzeMode:
14899       case AnalyzeFile:
14900         ExitAnalyzeMode();
14901         break;
14902       case EditGame:
14903       default:
14904         break;
14905     }
14906
14907 //    forwardMostMove = currentMove;
14908     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14909     startingEngine = TRUE;
14910
14911     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14912
14913     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14914     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14915       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14916       return;
14917     }
14918     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14919
14920     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14921                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14922         startingEngine = matchMode = FALSE;
14923         DisplayError("second engine does not play this", 0);
14924         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14925         EditGameEvent(); // switch back to EditGame mode
14926         return;
14927     }
14928
14929     if(!stalling) {
14930       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14931       SendToProgram("force\n", &second);
14932       stalling = 1;
14933       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14934       return;
14935     }
14936     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14937     if(appData.matchPause>10000 || appData.matchPause<10)
14938                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14939     wait = SubtractTimeMarks(&now, &pauseStart);
14940     if(wait < appData.matchPause) {
14941         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14942         return;
14943     }
14944     // we are now committed to starting the game
14945     stalling = 0;
14946     DisplayMessage("", "");
14947     if (startedFromSetupPosition) {
14948         SendBoard(&second, backwardMostMove);
14949     if (appData.debugMode) {
14950         fprintf(debugFP, "Two Machines\n");
14951     }
14952     }
14953     for (i = backwardMostMove; i < forwardMostMove; i++) {
14954         SendMoveToProgram(i, &second);
14955     }
14956
14957     gameMode = TwoMachinesPlay;
14958     pausing = startingEngine = FALSE;
14959     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14960     SetGameInfo();
14961     DisplayTwoMachinesTitle();
14962     firstMove = TRUE;
14963     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14964         onmove = &first;
14965     } else {
14966         onmove = &second;
14967     }
14968     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14969     SendToProgram(first.computerString, &first);
14970     if (first.sendName) {
14971       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14972       SendToProgram(buf, &first);
14973     }
14974     SendToProgram(second.computerString, &second);
14975     if (second.sendName) {
14976       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14977       SendToProgram(buf, &second);
14978     }
14979
14980     ResetClocks();
14981     if (!first.sendTime || !second.sendTime) {
14982         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14983         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14984     }
14985     if (onmove->sendTime) {
14986       if (onmove->useColors) {
14987         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14988       }
14989       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14990     }
14991     if (onmove->useColors) {
14992       SendToProgram(onmove->twoMachinesColor, onmove);
14993     }
14994     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14995 //    SendToProgram("go\n", onmove);
14996     onmove->maybeThinking = TRUE;
14997     SetMachineThinkingEnables();
14998
14999     StartClocks();
15000
15001     if(bookHit) { // [HGM] book: simulate book reply
15002         static char bookMove[MSG_SIZ]; // a bit generous?
15003
15004         programStats.nodes = programStats.depth = programStats.time =
15005         programStats.score = programStats.got_only_move = 0;
15006         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15007
15008         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15009         strcat(bookMove, bookHit);
15010         savedMessage = bookMove; // args for deferred call
15011         savedState = onmove;
15012         ScheduleDelayedEvent(DeferredBookMove, 1);
15013     }
15014 }
15015
15016 void
15017 TrainingEvent ()
15018 {
15019     if (gameMode == Training) {
15020       SetTrainingModeOff();
15021       gameMode = PlayFromGameFile;
15022       DisplayMessage("", _("Training mode off"));
15023     } else {
15024       gameMode = Training;
15025       animateTraining = appData.animate;
15026
15027       /* make sure we are not already at the end of the game */
15028       if (currentMove < forwardMostMove) {
15029         SetTrainingModeOn();
15030         DisplayMessage("", _("Training mode on"));
15031       } else {
15032         gameMode = PlayFromGameFile;
15033         DisplayError(_("Already at end of game"), 0);
15034       }
15035     }
15036     ModeHighlight();
15037 }
15038
15039 void
15040 IcsClientEvent ()
15041 {
15042     if (!appData.icsActive) return;
15043     switch (gameMode) {
15044       case IcsPlayingWhite:
15045       case IcsPlayingBlack:
15046       case IcsObserving:
15047       case IcsIdle:
15048       case BeginningOfGame:
15049       case IcsExamining:
15050         return;
15051
15052       case EditGame:
15053         break;
15054
15055       case EditPosition:
15056         EditPositionDone(TRUE);
15057         break;
15058
15059       case AnalyzeMode:
15060       case AnalyzeFile:
15061         ExitAnalyzeMode();
15062         break;
15063
15064       default:
15065         EditGameEvent();
15066         break;
15067     }
15068
15069     gameMode = IcsIdle;
15070     ModeHighlight();
15071     return;
15072 }
15073
15074 void
15075 EditGameEvent ()
15076 {
15077     int i;
15078
15079     switch (gameMode) {
15080       case Training:
15081         SetTrainingModeOff();
15082         break;
15083       case MachinePlaysWhite:
15084       case MachinePlaysBlack:
15085       case BeginningOfGame:
15086         SendToProgram("force\n", &first);
15087         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15088             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15089                 char buf[MSG_SIZ];
15090                 abortEngineThink = TRUE;
15091                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15092                 SendToProgram(buf, &first);
15093                 DisplayMessage("Aborting engine think", "");
15094                 FreezeUI();
15095             }
15096         }
15097         SetUserThinkingEnables();
15098         break;
15099       case PlayFromGameFile:
15100         (void) StopLoadGameTimer();
15101         if (gameFileFP != NULL) {
15102             gameFileFP = NULL;
15103         }
15104         break;
15105       case EditPosition:
15106         EditPositionDone(TRUE);
15107         break;
15108       case AnalyzeMode:
15109       case AnalyzeFile:
15110         ExitAnalyzeMode();
15111         SendToProgram("force\n", &first);
15112         break;
15113       case TwoMachinesPlay:
15114         GameEnds(EndOfFile, NULL, GE_PLAYER);
15115         ResurrectChessProgram();
15116         SetUserThinkingEnables();
15117         break;
15118       case EndOfGame:
15119         ResurrectChessProgram();
15120         break;
15121       case IcsPlayingBlack:
15122       case IcsPlayingWhite:
15123         DisplayError(_("Warning: You are still playing a game"), 0);
15124         break;
15125       case IcsObserving:
15126         DisplayError(_("Warning: You are still observing a game"), 0);
15127         break;
15128       case IcsExamining:
15129         DisplayError(_("Warning: You are still examining a game"), 0);
15130         break;
15131       case IcsIdle:
15132         break;
15133       case EditGame:
15134       default:
15135         return;
15136     }
15137
15138     pausing = FALSE;
15139     StopClocks();
15140     first.offeredDraw = second.offeredDraw = 0;
15141
15142     if (gameMode == PlayFromGameFile) {
15143         whiteTimeRemaining = timeRemaining[0][currentMove];
15144         blackTimeRemaining = timeRemaining[1][currentMove];
15145         DisplayTitle("");
15146     }
15147
15148     if (gameMode == MachinePlaysWhite ||
15149         gameMode == MachinePlaysBlack ||
15150         gameMode == TwoMachinesPlay ||
15151         gameMode == EndOfGame) {
15152         i = forwardMostMove;
15153         while (i > currentMove) {
15154             SendToProgram("undo\n", &first);
15155             i--;
15156         }
15157         if(!adjustedClock) {
15158         whiteTimeRemaining = timeRemaining[0][currentMove];
15159         blackTimeRemaining = timeRemaining[1][currentMove];
15160         DisplayBothClocks();
15161         }
15162         if (whiteFlag || blackFlag) {
15163             whiteFlag = blackFlag = 0;
15164         }
15165         DisplayTitle("");
15166     }
15167
15168     gameMode = EditGame;
15169     ModeHighlight();
15170     SetGameInfo();
15171 }
15172
15173
15174 void
15175 EditPositionEvent ()
15176 {
15177     if (gameMode == EditPosition) {
15178         EditGameEvent();
15179         return;
15180     }
15181
15182     EditGameEvent();
15183     if (gameMode != EditGame) return;
15184
15185     gameMode = EditPosition;
15186     ModeHighlight();
15187     SetGameInfo();
15188     if (currentMove > 0)
15189       CopyBoard(boards[0], boards[currentMove]);
15190
15191     blackPlaysFirst = !WhiteOnMove(currentMove);
15192     ResetClocks();
15193     currentMove = forwardMostMove = backwardMostMove = 0;
15194     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15195     DisplayMove(-1);
15196     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15197 }
15198
15199 void
15200 ExitAnalyzeMode ()
15201 {
15202     /* [DM] icsEngineAnalyze - possible call from other functions */
15203     if (appData.icsEngineAnalyze) {
15204         appData.icsEngineAnalyze = FALSE;
15205
15206         DisplayMessage("",_("Close ICS engine analyze..."));
15207     }
15208     if (first.analysisSupport && first.analyzing) {
15209       SendToBoth("exit\n");
15210       first.analyzing = second.analyzing = FALSE;
15211     }
15212     thinkOutput[0] = NULLCHAR;
15213 }
15214
15215 void
15216 EditPositionDone (Boolean fakeRights)
15217 {
15218     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15219
15220     startedFromSetupPosition = TRUE;
15221     InitChessProgram(&first, FALSE);
15222     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15223       boards[0][EP_STATUS] = EP_NONE;
15224       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15225       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15226         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15227         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15228       } else boards[0][CASTLING][2] = NoRights;
15229       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15230         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15231         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15232       } else boards[0][CASTLING][5] = NoRights;
15233       if(gameInfo.variant == VariantSChess) {
15234         int i;
15235         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15236           boards[0][VIRGIN][i] = 0;
15237           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15238           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15239         }
15240       }
15241     }
15242     SendToProgram("force\n", &first);
15243     if (blackPlaysFirst) {
15244         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15245         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15246         currentMove = forwardMostMove = backwardMostMove = 1;
15247         CopyBoard(boards[1], boards[0]);
15248     } else {
15249         currentMove = forwardMostMove = backwardMostMove = 0;
15250     }
15251     SendBoard(&first, forwardMostMove);
15252     if (appData.debugMode) {
15253         fprintf(debugFP, "EditPosDone\n");
15254     }
15255     DisplayTitle("");
15256     DisplayMessage("", "");
15257     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15258     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15259     gameMode = EditGame;
15260     ModeHighlight();
15261     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15262     ClearHighlights(); /* [AS] */
15263 }
15264
15265 /* Pause for `ms' milliseconds */
15266 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15267 void
15268 TimeDelay (long ms)
15269 {
15270     TimeMark m1, m2;
15271
15272     GetTimeMark(&m1);
15273     do {
15274         GetTimeMark(&m2);
15275     } while (SubtractTimeMarks(&m2, &m1) < ms);
15276 }
15277
15278 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15279 void
15280 SendMultiLineToICS (char *buf)
15281 {
15282     char temp[MSG_SIZ+1], *p;
15283     int len;
15284
15285     len = strlen(buf);
15286     if (len > MSG_SIZ)
15287       len = MSG_SIZ;
15288
15289     strncpy(temp, buf, len);
15290     temp[len] = 0;
15291
15292     p = temp;
15293     while (*p) {
15294         if (*p == '\n' || *p == '\r')
15295           *p = ' ';
15296         ++p;
15297     }
15298
15299     strcat(temp, "\n");
15300     SendToICS(temp);
15301     SendToPlayer(temp, strlen(temp));
15302 }
15303
15304 void
15305 SetWhiteToPlayEvent ()
15306 {
15307     if (gameMode == EditPosition) {
15308         blackPlaysFirst = FALSE;
15309         DisplayBothClocks();    /* works because currentMove is 0 */
15310     } else if (gameMode == IcsExamining) {
15311         SendToICS(ics_prefix);
15312         SendToICS("tomove white\n");
15313     }
15314 }
15315
15316 void
15317 SetBlackToPlayEvent ()
15318 {
15319     if (gameMode == EditPosition) {
15320         blackPlaysFirst = TRUE;
15321         currentMove = 1;        /* kludge */
15322         DisplayBothClocks();
15323         currentMove = 0;
15324     } else if (gameMode == IcsExamining) {
15325         SendToICS(ics_prefix);
15326         SendToICS("tomove black\n");
15327     }
15328 }
15329
15330 void
15331 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15332 {
15333     char buf[MSG_SIZ];
15334     ChessSquare piece = boards[0][y][x];
15335     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15336     static int lastVariant;
15337
15338     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15339
15340     switch (selection) {
15341       case ClearBoard:
15342         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15343         MarkTargetSquares(1);
15344         CopyBoard(currentBoard, boards[0]);
15345         CopyBoard(menuBoard, initialPosition);
15346         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15347             SendToICS(ics_prefix);
15348             SendToICS("bsetup clear\n");
15349         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15350             SendToICS(ics_prefix);
15351             SendToICS("clearboard\n");
15352         } else {
15353             int nonEmpty = 0;
15354             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15355                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15356                 for (y = 0; y < BOARD_HEIGHT; y++) {
15357                     if (gameMode == IcsExamining) {
15358                         if (boards[currentMove][y][x] != EmptySquare) {
15359                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15360                                     AAA + x, ONE + y);
15361                             SendToICS(buf);
15362                         }
15363                     } else if(boards[0][y][x] != DarkSquare) {
15364                         if(boards[0][y][x] != p) nonEmpty++;
15365                         boards[0][y][x] = p;
15366                     }
15367                 }
15368             }
15369             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15370                 int r;
15371                 for(r = 0; r < BOARD_HEIGHT; r++) {
15372                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15373                     ChessSquare p = menuBoard[r][x];
15374                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15375                   }
15376                 }
15377                 DisplayMessage("Clicking clock again restores position", "");
15378                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15379                 if(!nonEmpty) { // asked to clear an empty board
15380                     CopyBoard(boards[0], menuBoard);
15381                 } else
15382                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15383                     CopyBoard(boards[0], initialPosition);
15384                 } else
15385                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15386                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15387                     CopyBoard(boards[0], erasedBoard);
15388                 } else
15389                     CopyBoard(erasedBoard, currentBoard);
15390
15391             }
15392         }
15393         if (gameMode == EditPosition) {
15394             DrawPosition(FALSE, boards[0]);
15395         }
15396         break;
15397
15398       case WhitePlay:
15399         SetWhiteToPlayEvent();
15400         break;
15401
15402       case BlackPlay:
15403         SetBlackToPlayEvent();
15404         break;
15405
15406       case EmptySquare:
15407         if (gameMode == IcsExamining) {
15408             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15409             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15410             SendToICS(buf);
15411         } else {
15412             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15413                 if(x == BOARD_LEFT-2) {
15414                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15415                     boards[0][y][1] = 0;
15416                 } else
15417                 if(x == BOARD_RGHT+1) {
15418                     if(y >= gameInfo.holdingsSize) break;
15419                     boards[0][y][BOARD_WIDTH-2] = 0;
15420                 } else break;
15421             }
15422             boards[0][y][x] = EmptySquare;
15423             DrawPosition(FALSE, boards[0]);
15424         }
15425         break;
15426
15427       case PromotePiece:
15428         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15429            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15430             selection = (ChessSquare) (PROMOTED(piece));
15431         } else if(piece == EmptySquare) selection = WhiteSilver;
15432         else selection = (ChessSquare)((int)piece - 1);
15433         goto defaultlabel;
15434
15435       case DemotePiece:
15436         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15437            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15438             selection = (ChessSquare) (DEMOTED(piece));
15439         } else if(piece == EmptySquare) selection = BlackSilver;
15440         else selection = (ChessSquare)((int)piece + 1);
15441         goto defaultlabel;
15442
15443       case WhiteQueen:
15444       case BlackQueen:
15445         if(gameInfo.variant == VariantShatranj ||
15446            gameInfo.variant == VariantXiangqi  ||
15447            gameInfo.variant == VariantCourier  ||
15448            gameInfo.variant == VariantASEAN    ||
15449            gameInfo.variant == VariantMakruk     )
15450             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15451         goto defaultlabel;
15452
15453       case WhiteKing:
15454       case BlackKing:
15455         if(gameInfo.variant == VariantXiangqi)
15456             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15457         if(gameInfo.variant == VariantKnightmate)
15458             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15459       default:
15460         defaultlabel:
15461         if (gameMode == IcsExamining) {
15462             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15463             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15464                      PieceToChar(selection), AAA + x, ONE + y);
15465             SendToICS(buf);
15466         } else {
15467             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15468                 int n;
15469                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15470                     n = PieceToNumber(selection - BlackPawn);
15471                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15472                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15473                     boards[0][BOARD_HEIGHT-1-n][1]++;
15474                 } else
15475                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15476                     n = PieceToNumber(selection);
15477                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15478                     boards[0][n][BOARD_WIDTH-1] = selection;
15479                     boards[0][n][BOARD_WIDTH-2]++;
15480                 }
15481             } else
15482             boards[0][y][x] = selection;
15483             DrawPosition(TRUE, boards[0]);
15484             ClearHighlights();
15485             fromX = fromY = -1;
15486         }
15487         break;
15488     }
15489 }
15490
15491
15492 void
15493 DropMenuEvent (ChessSquare selection, int x, int y)
15494 {
15495     ChessMove moveType;
15496
15497     switch (gameMode) {
15498       case IcsPlayingWhite:
15499       case MachinePlaysBlack:
15500         if (!WhiteOnMove(currentMove)) {
15501             DisplayMoveError(_("It is Black's turn"));
15502             return;
15503         }
15504         moveType = WhiteDrop;
15505         break;
15506       case IcsPlayingBlack:
15507       case MachinePlaysWhite:
15508         if (WhiteOnMove(currentMove)) {
15509             DisplayMoveError(_("It is White's turn"));
15510             return;
15511         }
15512         moveType = BlackDrop;
15513         break;
15514       case EditGame:
15515         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15516         break;
15517       default:
15518         return;
15519     }
15520
15521     if (moveType == BlackDrop && selection < BlackPawn) {
15522       selection = (ChessSquare) ((int) selection
15523                                  + (int) BlackPawn - (int) WhitePawn);
15524     }
15525     if (boards[currentMove][y][x] != EmptySquare) {
15526         DisplayMoveError(_("That square is occupied"));
15527         return;
15528     }
15529
15530     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15531 }
15532
15533 void
15534 AcceptEvent ()
15535 {
15536     /* Accept a pending offer of any kind from opponent */
15537
15538     if (appData.icsActive) {
15539         SendToICS(ics_prefix);
15540         SendToICS("accept\n");
15541     } else if (cmailMsgLoaded) {
15542         if (currentMove == cmailOldMove &&
15543             commentList[cmailOldMove] != NULL &&
15544             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15545                    "Black offers a draw" : "White offers a draw")) {
15546             TruncateGame();
15547             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15548             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15549         } else {
15550             DisplayError(_("There is no pending offer on this move"), 0);
15551             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15552         }
15553     } else {
15554         /* Not used for offers from chess program */
15555     }
15556 }
15557
15558 void
15559 DeclineEvent ()
15560 {
15561     /* Decline a pending offer of any kind from opponent */
15562
15563     if (appData.icsActive) {
15564         SendToICS(ics_prefix);
15565         SendToICS("decline\n");
15566     } else if (cmailMsgLoaded) {
15567         if (currentMove == cmailOldMove &&
15568             commentList[cmailOldMove] != NULL &&
15569             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15570                    "Black offers a draw" : "White offers a draw")) {
15571 #ifdef NOTDEF
15572             AppendComment(cmailOldMove, "Draw declined", TRUE);
15573             DisplayComment(cmailOldMove - 1, "Draw declined");
15574 #endif /*NOTDEF*/
15575         } else {
15576             DisplayError(_("There is no pending offer on this move"), 0);
15577         }
15578     } else {
15579         /* Not used for offers from chess program */
15580     }
15581 }
15582
15583 void
15584 RematchEvent ()
15585 {
15586     /* Issue ICS rematch command */
15587     if (appData.icsActive) {
15588         SendToICS(ics_prefix);
15589         SendToICS("rematch\n");
15590     }
15591 }
15592
15593 void
15594 CallFlagEvent ()
15595 {
15596     /* Call your opponent's flag (claim a win on time) */
15597     if (appData.icsActive) {
15598         SendToICS(ics_prefix);
15599         SendToICS("flag\n");
15600     } else {
15601         switch (gameMode) {
15602           default:
15603             return;
15604           case MachinePlaysWhite:
15605             if (whiteFlag) {
15606                 if (blackFlag)
15607                   GameEnds(GameIsDrawn, "Both players ran out of time",
15608                            GE_PLAYER);
15609                 else
15610                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15611             } else {
15612                 DisplayError(_("Your opponent is not out of time"), 0);
15613             }
15614             break;
15615           case MachinePlaysBlack:
15616             if (blackFlag) {
15617                 if (whiteFlag)
15618                   GameEnds(GameIsDrawn, "Both players ran out of time",
15619                            GE_PLAYER);
15620                 else
15621                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15622             } else {
15623                 DisplayError(_("Your opponent is not out of time"), 0);
15624             }
15625             break;
15626         }
15627     }
15628 }
15629
15630 void
15631 ClockClick (int which)
15632 {       // [HGM] code moved to back-end from winboard.c
15633         if(which) { // black clock
15634           if (gameMode == EditPosition || gameMode == IcsExamining) {
15635             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15636             SetBlackToPlayEvent();
15637           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15638                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15639           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15640           } else if (shiftKey) {
15641             AdjustClock(which, -1);
15642           } else if (gameMode == IcsPlayingWhite ||
15643                      gameMode == MachinePlaysBlack) {
15644             CallFlagEvent();
15645           }
15646         } else { // white clock
15647           if (gameMode == EditPosition || gameMode == IcsExamining) {
15648             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15649             SetWhiteToPlayEvent();
15650           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15651                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15652           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15653           } else if (shiftKey) {
15654             AdjustClock(which, -1);
15655           } else if (gameMode == IcsPlayingBlack ||
15656                    gameMode == MachinePlaysWhite) {
15657             CallFlagEvent();
15658           }
15659         }
15660 }
15661
15662 void
15663 DrawEvent ()
15664 {
15665     /* Offer draw or accept pending draw offer from opponent */
15666
15667     if (appData.icsActive) {
15668         /* Note: tournament rules require draw offers to be
15669            made after you make your move but before you punch
15670            your clock.  Currently ICS doesn't let you do that;
15671            instead, you immediately punch your clock after making
15672            a move, but you can offer a draw at any time. */
15673
15674         SendToICS(ics_prefix);
15675         SendToICS("draw\n");
15676         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15677     } else if (cmailMsgLoaded) {
15678         if (currentMove == cmailOldMove &&
15679             commentList[cmailOldMove] != NULL &&
15680             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15681                    "Black offers a draw" : "White offers a draw")) {
15682             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15683             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15684         } else if (currentMove == cmailOldMove + 1) {
15685             char *offer = WhiteOnMove(cmailOldMove) ?
15686               "White offers a draw" : "Black offers a draw";
15687             AppendComment(currentMove, offer, TRUE);
15688             DisplayComment(currentMove - 1, offer);
15689             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15690         } else {
15691             DisplayError(_("You must make your move before offering a draw"), 0);
15692             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15693         }
15694     } else if (first.offeredDraw) {
15695         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15696     } else {
15697         if (first.sendDrawOffers) {
15698             SendToProgram("draw\n", &first);
15699             userOfferedDraw = TRUE;
15700         }
15701     }
15702 }
15703
15704 void
15705 AdjournEvent ()
15706 {
15707     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15708
15709     if (appData.icsActive) {
15710         SendToICS(ics_prefix);
15711         SendToICS("adjourn\n");
15712     } else {
15713         /* Currently GNU Chess doesn't offer or accept Adjourns */
15714     }
15715 }
15716
15717
15718 void
15719 AbortEvent ()
15720 {
15721     /* Offer Abort or accept pending Abort offer from opponent */
15722
15723     if (appData.icsActive) {
15724         SendToICS(ics_prefix);
15725         SendToICS("abort\n");
15726     } else {
15727         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15728     }
15729 }
15730
15731 void
15732 ResignEvent ()
15733 {
15734     /* Resign.  You can do this even if it's not your turn. */
15735
15736     if (appData.icsActive) {
15737         SendToICS(ics_prefix);
15738         SendToICS("resign\n");
15739     } else {
15740         switch (gameMode) {
15741           case MachinePlaysWhite:
15742             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15743             break;
15744           case MachinePlaysBlack:
15745             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15746             break;
15747           case EditGame:
15748             if (cmailMsgLoaded) {
15749                 TruncateGame();
15750                 if (WhiteOnMove(cmailOldMove)) {
15751                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15752                 } else {
15753                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15754                 }
15755                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15756             }
15757             break;
15758           default:
15759             break;
15760         }
15761     }
15762 }
15763
15764
15765 void
15766 StopObservingEvent ()
15767 {
15768     /* Stop observing current games */
15769     SendToICS(ics_prefix);
15770     SendToICS("unobserve\n");
15771 }
15772
15773 void
15774 StopExaminingEvent ()
15775 {
15776     /* Stop observing current game */
15777     SendToICS(ics_prefix);
15778     SendToICS("unexamine\n");
15779 }
15780
15781 void
15782 ForwardInner (int target)
15783 {
15784     int limit; int oldSeekGraphUp = seekGraphUp;
15785
15786     if (appData.debugMode)
15787         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15788                 target, currentMove, forwardMostMove);
15789
15790     if (gameMode == EditPosition)
15791       return;
15792
15793     seekGraphUp = FALSE;
15794     MarkTargetSquares(1);
15795     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15796
15797     if (gameMode == PlayFromGameFile && !pausing)
15798       PauseEvent();
15799
15800     if (gameMode == IcsExamining && pausing)
15801       limit = pauseExamForwardMostMove;
15802     else
15803       limit = forwardMostMove;
15804
15805     if (target > limit) target = limit;
15806
15807     if (target > 0 && moveList[target - 1][0]) {
15808         int fromX, fromY, toX, toY;
15809         toX = moveList[target - 1][2] - AAA;
15810         toY = moveList[target - 1][3] - ONE;
15811         if (moveList[target - 1][1] == '@') {
15812             if (appData.highlightLastMove) {
15813                 SetHighlights(-1, -1, toX, toY);
15814             }
15815         } else {
15816             int viaX = moveList[target - 1][5] - AAA;
15817             int viaY = moveList[target - 1][6] - ONE;
15818             fromX = moveList[target - 1][0] - AAA;
15819             fromY = moveList[target - 1][1] - ONE;
15820             if (target == currentMove + 1) {
15821                 if(moveList[target - 1][4] == ';') { // multi-leg
15822                     ChessSquare piece = boards[currentMove][viaY][viaX];
15823                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15824                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15825                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15826                     boards[currentMove][viaY][viaX] = piece;
15827                 } else
15828                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15829             }
15830             if (appData.highlightLastMove) {
15831                 SetHighlights(fromX, fromY, toX, toY);
15832             }
15833         }
15834     }
15835     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15836         gameMode == Training || gameMode == PlayFromGameFile ||
15837         gameMode == AnalyzeFile) {
15838         while (currentMove < target) {
15839             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15840             SendMoveToProgram(currentMove++, &first);
15841         }
15842     } else {
15843         currentMove = target;
15844     }
15845
15846     if (gameMode == EditGame || gameMode == EndOfGame) {
15847         whiteTimeRemaining = timeRemaining[0][currentMove];
15848         blackTimeRemaining = timeRemaining[1][currentMove];
15849     }
15850     DisplayBothClocks();
15851     DisplayMove(currentMove - 1);
15852     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15853     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15854     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15855         DisplayComment(currentMove - 1, commentList[currentMove]);
15856     }
15857     ClearMap(); // [HGM] exclude: invalidate map
15858 }
15859
15860
15861 void
15862 ForwardEvent ()
15863 {
15864     if (gameMode == IcsExamining && !pausing) {
15865         SendToICS(ics_prefix);
15866         SendToICS("forward\n");
15867     } else {
15868         ForwardInner(currentMove + 1);
15869     }
15870 }
15871
15872 void
15873 ToEndEvent ()
15874 {
15875     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15876         /* to optimze, we temporarily turn off analysis mode while we feed
15877          * the remaining moves to the engine. Otherwise we get analysis output
15878          * after each move.
15879          */
15880         if (first.analysisSupport) {
15881           SendToProgram("exit\nforce\n", &first);
15882           first.analyzing = FALSE;
15883         }
15884     }
15885
15886     if (gameMode == IcsExamining && !pausing) {
15887         SendToICS(ics_prefix);
15888         SendToICS("forward 999999\n");
15889     } else {
15890         ForwardInner(forwardMostMove);
15891     }
15892
15893     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15894         /* we have fed all the moves, so reactivate analysis mode */
15895         SendToProgram("analyze\n", &first);
15896         first.analyzing = TRUE;
15897         /*first.maybeThinking = TRUE;*/
15898         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15899     }
15900 }
15901
15902 void
15903 BackwardInner (int target)
15904 {
15905     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15906
15907     if (appData.debugMode)
15908         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15909                 target, currentMove, forwardMostMove);
15910
15911     if (gameMode == EditPosition) return;
15912     seekGraphUp = FALSE;
15913     MarkTargetSquares(1);
15914     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15915     if (currentMove <= backwardMostMove) {
15916         ClearHighlights();
15917         DrawPosition(full_redraw, boards[currentMove]);
15918         return;
15919     }
15920     if (gameMode == PlayFromGameFile && !pausing)
15921       PauseEvent();
15922
15923     if (moveList[target][0]) {
15924         int fromX, fromY, toX, toY;
15925         toX = moveList[target][2] - AAA;
15926         toY = moveList[target][3] - ONE;
15927         if (moveList[target][1] == '@') {
15928             if (appData.highlightLastMove) {
15929                 SetHighlights(-1, -1, toX, toY);
15930             }
15931         } else {
15932             fromX = moveList[target][0] - AAA;
15933             fromY = moveList[target][1] - ONE;
15934             if (target == currentMove - 1) {
15935                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15936             }
15937             if (appData.highlightLastMove) {
15938                 SetHighlights(fromX, fromY, toX, toY);
15939             }
15940         }
15941     }
15942     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15943         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15944         while (currentMove > target) {
15945             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15946                 // null move cannot be undone. Reload program with move history before it.
15947                 int i;
15948                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15949                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15950                 }
15951                 SendBoard(&first, i);
15952               if(second.analyzing) SendBoard(&second, i);
15953                 for(currentMove=i; currentMove<target; currentMove++) {
15954                     SendMoveToProgram(currentMove, &first);
15955                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15956                 }
15957                 break;
15958             }
15959             SendToBoth("undo\n");
15960             currentMove--;
15961         }
15962     } else {
15963         currentMove = target;
15964     }
15965
15966     if (gameMode == EditGame || gameMode == EndOfGame) {
15967         whiteTimeRemaining = timeRemaining[0][currentMove];
15968         blackTimeRemaining = timeRemaining[1][currentMove];
15969     }
15970     DisplayBothClocks();
15971     DisplayMove(currentMove - 1);
15972     DrawPosition(full_redraw, boards[currentMove]);
15973     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15974     // [HGM] PV info: routine tests if comment empty
15975     DisplayComment(currentMove - 1, commentList[currentMove]);
15976     ClearMap(); // [HGM] exclude: invalidate map
15977 }
15978
15979 void
15980 BackwardEvent ()
15981 {
15982     if (gameMode == IcsExamining && !pausing) {
15983         SendToICS(ics_prefix);
15984         SendToICS("backward\n");
15985     } else {
15986         BackwardInner(currentMove - 1);
15987     }
15988 }
15989
15990 void
15991 ToStartEvent ()
15992 {
15993     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15994         /* to optimize, we temporarily turn off analysis mode while we undo
15995          * all the moves. Otherwise we get analysis output after each undo.
15996          */
15997         if (first.analysisSupport) {
15998           SendToProgram("exit\nforce\n", &first);
15999           first.analyzing = FALSE;
16000         }
16001     }
16002
16003     if (gameMode == IcsExamining && !pausing) {
16004         SendToICS(ics_prefix);
16005         SendToICS("backward 999999\n");
16006     } else {
16007         BackwardInner(backwardMostMove);
16008     }
16009
16010     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16011         /* we have fed all the moves, so reactivate analysis mode */
16012         SendToProgram("analyze\n", &first);
16013         first.analyzing = TRUE;
16014         /*first.maybeThinking = TRUE;*/
16015         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16016     }
16017 }
16018
16019 void
16020 ToNrEvent (int to)
16021 {
16022   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16023   if (to >= forwardMostMove) to = forwardMostMove;
16024   if (to <= backwardMostMove) to = backwardMostMove;
16025   if (to < currentMove) {
16026     BackwardInner(to);
16027   } else {
16028     ForwardInner(to);
16029   }
16030 }
16031
16032 void
16033 RevertEvent (Boolean annotate)
16034 {
16035     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16036         return;
16037     }
16038     if (gameMode != IcsExamining) {
16039         DisplayError(_("You are not examining a game"), 0);
16040         return;
16041     }
16042     if (pausing) {
16043         DisplayError(_("You can't revert while pausing"), 0);
16044         return;
16045     }
16046     SendToICS(ics_prefix);
16047     SendToICS("revert\n");
16048 }
16049
16050 void
16051 RetractMoveEvent ()
16052 {
16053     switch (gameMode) {
16054       case MachinePlaysWhite:
16055       case MachinePlaysBlack:
16056         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16057             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16058             return;
16059         }
16060         if (forwardMostMove < 2) return;
16061         currentMove = forwardMostMove = forwardMostMove - 2;
16062         whiteTimeRemaining = timeRemaining[0][currentMove];
16063         blackTimeRemaining = timeRemaining[1][currentMove];
16064         DisplayBothClocks();
16065         DisplayMove(currentMove - 1);
16066         ClearHighlights();/*!! could figure this out*/
16067         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16068         SendToProgram("remove\n", &first);
16069         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16070         break;
16071
16072       case BeginningOfGame:
16073       default:
16074         break;
16075
16076       case IcsPlayingWhite:
16077       case IcsPlayingBlack:
16078         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16079             SendToICS(ics_prefix);
16080             SendToICS("takeback 2\n");
16081         } else {
16082             SendToICS(ics_prefix);
16083             SendToICS("takeback 1\n");
16084         }
16085         break;
16086     }
16087 }
16088
16089 void
16090 MoveNowEvent ()
16091 {
16092     ChessProgramState *cps;
16093
16094     switch (gameMode) {
16095       case MachinePlaysWhite:
16096         if (!WhiteOnMove(forwardMostMove)) {
16097             DisplayError(_("It is your turn"), 0);
16098             return;
16099         }
16100         cps = &first;
16101         break;
16102       case MachinePlaysBlack:
16103         if (WhiteOnMove(forwardMostMove)) {
16104             DisplayError(_("It is your turn"), 0);
16105             return;
16106         }
16107         cps = &first;
16108         break;
16109       case TwoMachinesPlay:
16110         if (WhiteOnMove(forwardMostMove) ==
16111             (first.twoMachinesColor[0] == 'w')) {
16112             cps = &first;
16113         } else {
16114             cps = &second;
16115         }
16116         break;
16117       case BeginningOfGame:
16118       default:
16119         return;
16120     }
16121     SendToProgram("?\n", cps);
16122 }
16123
16124 void
16125 TruncateGameEvent ()
16126 {
16127     EditGameEvent();
16128     if (gameMode != EditGame) return;
16129     TruncateGame();
16130 }
16131
16132 void
16133 TruncateGame ()
16134 {
16135     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16136     if (forwardMostMove > currentMove) {
16137         if (gameInfo.resultDetails != NULL) {
16138             free(gameInfo.resultDetails);
16139             gameInfo.resultDetails = NULL;
16140             gameInfo.result = GameUnfinished;
16141         }
16142         forwardMostMove = currentMove;
16143         HistorySet(parseList, backwardMostMove, forwardMostMove,
16144                    currentMove-1);
16145     }
16146 }
16147
16148 void
16149 HintEvent ()
16150 {
16151     if (appData.noChessProgram) return;
16152     switch (gameMode) {
16153       case MachinePlaysWhite:
16154         if (WhiteOnMove(forwardMostMove)) {
16155             DisplayError(_("Wait until your turn."), 0);
16156             return;
16157         }
16158         break;
16159       case BeginningOfGame:
16160       case MachinePlaysBlack:
16161         if (!WhiteOnMove(forwardMostMove)) {
16162             DisplayError(_("Wait until your turn."), 0);
16163             return;
16164         }
16165         break;
16166       default:
16167         DisplayError(_("No hint available"), 0);
16168         return;
16169     }
16170     SendToProgram("hint\n", &first);
16171     hintRequested = TRUE;
16172 }
16173
16174 int
16175 SaveSelected (FILE *g, int dummy, char *dummy2)
16176 {
16177     ListGame * lg = (ListGame *) gameList.head;
16178     int nItem, cnt=0;
16179     FILE *f;
16180
16181     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16182         DisplayError(_("Game list not loaded or empty"), 0);
16183         return 0;
16184     }
16185
16186     creatingBook = TRUE; // suppresses stuff during load game
16187
16188     /* Get list size */
16189     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16190         if(lg->position >= 0) { // selected?
16191             LoadGame(f, nItem, "", TRUE);
16192             SaveGamePGN2(g); // leaves g open
16193             cnt++; DoEvents();
16194         }
16195         lg = (ListGame *) lg->node.succ;
16196     }
16197
16198     fclose(g);
16199     creatingBook = FALSE;
16200
16201     return cnt;
16202 }
16203
16204 void
16205 CreateBookEvent ()
16206 {
16207     ListGame * lg = (ListGame *) gameList.head;
16208     FILE *f, *g;
16209     int nItem;
16210     static int secondTime = FALSE;
16211
16212     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16213         DisplayError(_("Game list not loaded or empty"), 0);
16214         return;
16215     }
16216
16217     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16218         fclose(g);
16219         secondTime++;
16220         DisplayNote(_("Book file exists! Try again for overwrite."));
16221         return;
16222     }
16223
16224     creatingBook = TRUE;
16225     secondTime = FALSE;
16226
16227     /* Get list size */
16228     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16229         if(lg->position >= 0) {
16230             LoadGame(f, nItem, "", TRUE);
16231             AddGameToBook(TRUE);
16232             DoEvents();
16233         }
16234         lg = (ListGame *) lg->node.succ;
16235     }
16236
16237     creatingBook = FALSE;
16238     FlushBook();
16239 }
16240
16241 void
16242 BookEvent ()
16243 {
16244     if (appData.noChessProgram) return;
16245     switch (gameMode) {
16246       case MachinePlaysWhite:
16247         if (WhiteOnMove(forwardMostMove)) {
16248             DisplayError(_("Wait until your turn."), 0);
16249             return;
16250         }
16251         break;
16252       case BeginningOfGame:
16253       case MachinePlaysBlack:
16254         if (!WhiteOnMove(forwardMostMove)) {
16255             DisplayError(_("Wait until your turn."), 0);
16256             return;
16257         }
16258         break;
16259       case EditPosition:
16260         EditPositionDone(TRUE);
16261         break;
16262       case TwoMachinesPlay:
16263         return;
16264       default:
16265         break;
16266     }
16267     SendToProgram("bk\n", &first);
16268     bookOutput[0] = NULLCHAR;
16269     bookRequested = TRUE;
16270 }
16271
16272 void
16273 AboutGameEvent ()
16274 {
16275     char *tags = PGNTags(&gameInfo);
16276     TagsPopUp(tags, CmailMsg());
16277     free(tags);
16278 }
16279
16280 /* end button procedures */
16281
16282 void
16283 PrintPosition (FILE *fp, int move)
16284 {
16285     int i, j;
16286
16287     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16288         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16289             char c = PieceToChar(boards[move][i][j]);
16290             fputc(c == '?' ? '.' : c, fp);
16291             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16292         }
16293     }
16294     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16295       fprintf(fp, "white to play\n");
16296     else
16297       fprintf(fp, "black to play\n");
16298 }
16299
16300 void
16301 PrintOpponents (FILE *fp)
16302 {
16303     if (gameInfo.white != NULL) {
16304         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16305     } else {
16306         fprintf(fp, "\n");
16307     }
16308 }
16309
16310 /* Find last component of program's own name, using some heuristics */
16311 void
16312 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16313 {
16314     char *p, *q, c;
16315     int local = (strcmp(host, "localhost") == 0);
16316     while (!local && (p = strchr(prog, ';')) != NULL) {
16317         p++;
16318         while (*p == ' ') p++;
16319         prog = p;
16320     }
16321     if (*prog == '"' || *prog == '\'') {
16322         q = strchr(prog + 1, *prog);
16323     } else {
16324         q = strchr(prog, ' ');
16325     }
16326     if (q == NULL) q = prog + strlen(prog);
16327     p = q;
16328     while (p >= prog && *p != '/' && *p != '\\') p--;
16329     p++;
16330     if(p == prog && *p == '"') p++;
16331     c = *q; *q = 0;
16332     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16333     memcpy(buf, p, q - p);
16334     buf[q - p] = NULLCHAR;
16335     if (!local) {
16336         strcat(buf, "@");
16337         strcat(buf, host);
16338     }
16339 }
16340
16341 char *
16342 TimeControlTagValue ()
16343 {
16344     char buf[MSG_SIZ];
16345     if (!appData.clockMode) {
16346       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16347     } else if (movesPerSession > 0) {
16348       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16349     } else if (timeIncrement == 0) {
16350       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16351     } else {
16352       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16353     }
16354     return StrSave(buf);
16355 }
16356
16357 void
16358 SetGameInfo ()
16359 {
16360     /* This routine is used only for certain modes */
16361     VariantClass v = gameInfo.variant;
16362     ChessMove r = GameUnfinished;
16363     char *p = NULL;
16364
16365     if(keepInfo) return;
16366
16367     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16368         r = gameInfo.result;
16369         p = gameInfo.resultDetails;
16370         gameInfo.resultDetails = NULL;
16371     }
16372     ClearGameInfo(&gameInfo);
16373     gameInfo.variant = v;
16374
16375     switch (gameMode) {
16376       case MachinePlaysWhite:
16377         gameInfo.event = StrSave( appData.pgnEventHeader );
16378         gameInfo.site = StrSave(HostName());
16379         gameInfo.date = PGNDate();
16380         gameInfo.round = StrSave("-");
16381         gameInfo.white = StrSave(first.tidy);
16382         gameInfo.black = StrSave(UserName());
16383         gameInfo.timeControl = TimeControlTagValue();
16384         break;
16385
16386       case MachinePlaysBlack:
16387         gameInfo.event = StrSave( appData.pgnEventHeader );
16388         gameInfo.site = StrSave(HostName());
16389         gameInfo.date = PGNDate();
16390         gameInfo.round = StrSave("-");
16391         gameInfo.white = StrSave(UserName());
16392         gameInfo.black = StrSave(first.tidy);
16393         gameInfo.timeControl = TimeControlTagValue();
16394         break;
16395
16396       case TwoMachinesPlay:
16397         gameInfo.event = StrSave( appData.pgnEventHeader );
16398         gameInfo.site = StrSave(HostName());
16399         gameInfo.date = PGNDate();
16400         if (roundNr > 0) {
16401             char buf[MSG_SIZ];
16402             snprintf(buf, MSG_SIZ, "%d", roundNr);
16403             gameInfo.round = StrSave(buf);
16404         } else {
16405             gameInfo.round = StrSave("-");
16406         }
16407         if (first.twoMachinesColor[0] == 'w') {
16408             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16409             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16410         } else {
16411             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16412             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16413         }
16414         gameInfo.timeControl = TimeControlTagValue();
16415         break;
16416
16417       case EditGame:
16418         gameInfo.event = StrSave("Edited game");
16419         gameInfo.site = StrSave(HostName());
16420         gameInfo.date = PGNDate();
16421         gameInfo.round = StrSave("-");
16422         gameInfo.white = StrSave("-");
16423         gameInfo.black = StrSave("-");
16424         gameInfo.result = r;
16425         gameInfo.resultDetails = p;
16426         break;
16427
16428       case EditPosition:
16429         gameInfo.event = StrSave("Edited position");
16430         gameInfo.site = StrSave(HostName());
16431         gameInfo.date = PGNDate();
16432         gameInfo.round = StrSave("-");
16433         gameInfo.white = StrSave("-");
16434         gameInfo.black = StrSave("-");
16435         break;
16436
16437       case IcsPlayingWhite:
16438       case IcsPlayingBlack:
16439       case IcsObserving:
16440       case IcsExamining:
16441         break;
16442
16443       case PlayFromGameFile:
16444         gameInfo.event = StrSave("Game from non-PGN file");
16445         gameInfo.site = StrSave(HostName());
16446         gameInfo.date = PGNDate();
16447         gameInfo.round = StrSave("-");
16448         gameInfo.white = StrSave("?");
16449         gameInfo.black = StrSave("?");
16450         break;
16451
16452       default:
16453         break;
16454     }
16455 }
16456
16457 void
16458 ReplaceComment (int index, char *text)
16459 {
16460     int len;
16461     char *p;
16462     float score;
16463
16464     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16465        pvInfoList[index-1].depth == len &&
16466        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16467        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16468     while (*text == '\n') text++;
16469     len = strlen(text);
16470     while (len > 0 && text[len - 1] == '\n') len--;
16471
16472     if (commentList[index] != NULL)
16473       free(commentList[index]);
16474
16475     if (len == 0) {
16476         commentList[index] = NULL;
16477         return;
16478     }
16479   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16480       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16481       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16482     commentList[index] = (char *) malloc(len + 2);
16483     strncpy(commentList[index], text, len);
16484     commentList[index][len] = '\n';
16485     commentList[index][len + 1] = NULLCHAR;
16486   } else {
16487     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16488     char *p;
16489     commentList[index] = (char *) malloc(len + 7);
16490     safeStrCpy(commentList[index], "{\n", 3);
16491     safeStrCpy(commentList[index]+2, text, len+1);
16492     commentList[index][len+2] = NULLCHAR;
16493     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16494     strcat(commentList[index], "\n}\n");
16495   }
16496 }
16497
16498 void
16499 CrushCRs (char *text)
16500 {
16501   char *p = text;
16502   char *q = text;
16503   char ch;
16504
16505   do {
16506     ch = *p++;
16507     if (ch == '\r') continue;
16508     *q++ = ch;
16509   } while (ch != '\0');
16510 }
16511
16512 void
16513 AppendComment (int index, char *text, Boolean addBraces)
16514 /* addBraces  tells if we should add {} */
16515 {
16516     int oldlen, len;
16517     char *old;
16518
16519 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16520     if(addBraces == 3) addBraces = 0; else // force appending literally
16521     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16522
16523     CrushCRs(text);
16524     while (*text == '\n') text++;
16525     len = strlen(text);
16526     while (len > 0 && text[len - 1] == '\n') len--;
16527     text[len] = NULLCHAR;
16528
16529     if (len == 0) return;
16530
16531     if (commentList[index] != NULL) {
16532       Boolean addClosingBrace = addBraces;
16533         old = commentList[index];
16534         oldlen = strlen(old);
16535         while(commentList[index][oldlen-1] ==  '\n')
16536           commentList[index][--oldlen] = NULLCHAR;
16537         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16538         safeStrCpy(commentList[index], old, oldlen + len + 6);
16539         free(old);
16540         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16541         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16542           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16543           while (*text == '\n') { text++; len--; }
16544           commentList[index][--oldlen] = NULLCHAR;
16545       }
16546         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16547         else          strcat(commentList[index], "\n");
16548         strcat(commentList[index], text);
16549         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16550         else          strcat(commentList[index], "\n");
16551     } else {
16552         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16553         if(addBraces)
16554           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16555         else commentList[index][0] = NULLCHAR;
16556         strcat(commentList[index], text);
16557         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16558         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16559     }
16560 }
16561
16562 static char *
16563 FindStr (char * text, char * sub_text)
16564 {
16565     char * result = strstr( text, sub_text );
16566
16567     if( result != NULL ) {
16568         result += strlen( sub_text );
16569     }
16570
16571     return result;
16572 }
16573
16574 /* [AS] Try to extract PV info from PGN comment */
16575 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16576 char *
16577 GetInfoFromComment (int index, char * text)
16578 {
16579     char * sep = text, *p;
16580
16581     if( text != NULL && index > 0 ) {
16582         int score = 0;
16583         int depth = 0;
16584         int time = -1, sec = 0, deci;
16585         char * s_eval = FindStr( text, "[%eval " );
16586         char * s_emt = FindStr( text, "[%emt " );
16587 #if 0
16588         if( s_eval != NULL || s_emt != NULL ) {
16589 #else
16590         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16591 #endif
16592             /* New style */
16593             char delim;
16594
16595             if( s_eval != NULL ) {
16596                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16597                     return text;
16598                 }
16599
16600                 if( delim != ']' ) {
16601                     return text;
16602                 }
16603             }
16604
16605             if( s_emt != NULL ) {
16606             }
16607                 return text;
16608         }
16609         else {
16610             /* We expect something like: [+|-]nnn.nn/dd */
16611             int score_lo = 0;
16612
16613             if(*text != '{') return text; // [HGM] braces: must be normal comment
16614
16615             sep = strchr( text, '/' );
16616             if( sep == NULL || sep < (text+4) ) {
16617                 return text;
16618             }
16619
16620             p = text;
16621             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16622             if(p[1] == '(') { // comment starts with PV
16623                p = strchr(p, ')'); // locate end of PV
16624                if(p == NULL || sep < p+5) return text;
16625                // at this point we have something like "{(.*) +0.23/6 ..."
16626                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16627                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16628                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16629             }
16630             time = -1; sec = -1; deci = -1;
16631             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16632                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16633                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16634                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16635                 return text;
16636             }
16637
16638             if( score_lo < 0 || score_lo >= 100 ) {
16639                 return text;
16640             }
16641
16642             if(sec >= 0) time = 600*time + 10*sec; else
16643             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16644
16645             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16646
16647             /* [HGM] PV time: now locate end of PV info */
16648             while( *++sep >= '0' && *sep <= '9'); // strip depth
16649             if(time >= 0)
16650             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16651             if(sec >= 0)
16652             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16653             if(deci >= 0)
16654             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16655             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16656         }
16657
16658         if( depth <= 0 ) {
16659             return text;
16660         }
16661
16662         if( time < 0 ) {
16663             time = -1;
16664         }
16665
16666         pvInfoList[index-1].depth = depth;
16667         pvInfoList[index-1].score = score;
16668         pvInfoList[index-1].time  = 10*time; // centi-sec
16669         if(*sep == '}') *sep = 0; else *--sep = '{';
16670         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16671     }
16672     return sep;
16673 }
16674
16675 void
16676 SendToProgram (char *message, ChessProgramState *cps)
16677 {
16678     int count, outCount, error;
16679     char buf[MSG_SIZ];
16680
16681     if (cps->pr == NoProc) return;
16682     Attention(cps);
16683
16684     if (appData.debugMode) {
16685         TimeMark now;
16686         GetTimeMark(&now);
16687         fprintf(debugFP, "%ld >%-6s: %s",
16688                 SubtractTimeMarks(&now, &programStartTime),
16689                 cps->which, message);
16690         if(serverFP)
16691             fprintf(serverFP, "%ld >%-6s: %s",
16692                 SubtractTimeMarks(&now, &programStartTime),
16693                 cps->which, message), fflush(serverFP);
16694     }
16695
16696     count = strlen(message);
16697     outCount = OutputToProcess(cps->pr, message, count, &error);
16698     if (outCount < count && !exiting
16699                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16700       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16701       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16702         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16703             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16704                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16705                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16706                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16707             } else {
16708                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16709                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16710                 gameInfo.result = res;
16711             }
16712             gameInfo.resultDetails = StrSave(buf);
16713         }
16714         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16715         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16716     }
16717 }
16718
16719 void
16720 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16721 {
16722     char *end_str;
16723     char buf[MSG_SIZ];
16724     ChessProgramState *cps = (ChessProgramState *)closure;
16725
16726     if (isr != cps->isr) return; /* Killed intentionally */
16727     if (count <= 0) {
16728         if (count == 0) {
16729             RemoveInputSource(cps->isr);
16730             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16731                     _(cps->which), cps->program);
16732             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16733             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16734                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16735                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16736                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16737                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16738                 } else {
16739                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16740                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16741                     gameInfo.result = res;
16742                 }
16743                 gameInfo.resultDetails = StrSave(buf);
16744             }
16745             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16746             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16747         } else {
16748             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16749                     _(cps->which), cps->program);
16750             RemoveInputSource(cps->isr);
16751
16752             /* [AS] Program is misbehaving badly... kill it */
16753             if( count == -2 ) {
16754                 DestroyChildProcess( cps->pr, 9 );
16755                 cps->pr = NoProc;
16756             }
16757
16758             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16759         }
16760         return;
16761     }
16762
16763     if ((end_str = strchr(message, '\r')) != NULL)
16764       *end_str = NULLCHAR;
16765     if ((end_str = strchr(message, '\n')) != NULL)
16766       *end_str = NULLCHAR;
16767
16768     if (appData.debugMode) {
16769         TimeMark now; int print = 1;
16770         char *quote = ""; char c; int i;
16771
16772         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16773                 char start = message[0];
16774                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16775                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16776                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16777                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16778                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16779                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16780                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16781                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16782                    sscanf(message, "hint: %c", &c)!=1 &&
16783                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16784                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16785                     print = (appData.engineComments >= 2);
16786                 }
16787                 message[0] = start; // restore original message
16788         }
16789         if(print) {
16790                 GetTimeMark(&now);
16791                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16792                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16793                         quote,
16794                         message);
16795                 if(serverFP)
16796                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16797                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16798                         quote,
16799                         message), fflush(serverFP);
16800         }
16801     }
16802
16803     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16804     if (appData.icsEngineAnalyze) {
16805         if (strstr(message, "whisper") != NULL ||
16806              strstr(message, "kibitz") != NULL ||
16807             strstr(message, "tellics") != NULL) return;
16808     }
16809
16810     HandleMachineMove(message, cps);
16811 }
16812
16813
16814 void
16815 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16816 {
16817     char buf[MSG_SIZ];
16818     int seconds;
16819
16820     if( timeControl_2 > 0 ) {
16821         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16822             tc = timeControl_2;
16823         }
16824     }
16825     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16826     inc /= cps->timeOdds;
16827     st  /= cps->timeOdds;
16828
16829     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16830
16831     if (st > 0) {
16832       /* Set exact time per move, normally using st command */
16833       if (cps->stKludge) {
16834         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16835         seconds = st % 60;
16836         if (seconds == 0) {
16837           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16838         } else {
16839           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16840         }
16841       } else {
16842         snprintf(buf, MSG_SIZ, "st %d\n", st);
16843       }
16844     } else {
16845       /* Set conventional or incremental time control, using level command */
16846       if (seconds == 0) {
16847         /* Note old gnuchess bug -- minutes:seconds used to not work.
16848            Fixed in later versions, but still avoid :seconds
16849            when seconds is 0. */
16850         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16851       } else {
16852         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16853                  seconds, inc/1000.);
16854       }
16855     }
16856     SendToProgram(buf, cps);
16857
16858     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16859     /* Orthogonally, limit search to given depth */
16860     if (sd > 0) {
16861       if (cps->sdKludge) {
16862         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16863       } else {
16864         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16865       }
16866       SendToProgram(buf, cps);
16867     }
16868
16869     if(cps->nps >= 0) { /* [HGM] nps */
16870         if(cps->supportsNPS == FALSE)
16871           cps->nps = -1; // don't use if engine explicitly says not supported!
16872         else {
16873           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16874           SendToProgram(buf, cps);
16875         }
16876     }
16877 }
16878
16879 ChessProgramState *
16880 WhitePlayer ()
16881 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16882 {
16883     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16884        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16885         return &second;
16886     return &first;
16887 }
16888
16889 void
16890 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16891 {
16892     char message[MSG_SIZ];
16893     long time, otime;
16894
16895     /* Note: this routine must be called when the clocks are stopped
16896        or when they have *just* been set or switched; otherwise
16897        it will be off by the time since the current tick started.
16898     */
16899     if (machineWhite) {
16900         time = whiteTimeRemaining / 10;
16901         otime = blackTimeRemaining / 10;
16902     } else {
16903         time = blackTimeRemaining / 10;
16904         otime = whiteTimeRemaining / 10;
16905     }
16906     /* [HGM] translate opponent's time by time-odds factor */
16907     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16908
16909     if (time <= 0) time = 1;
16910     if (otime <= 0) otime = 1;
16911
16912     snprintf(message, MSG_SIZ, "time %ld\n", time);
16913     SendToProgram(message, cps);
16914
16915     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16916     SendToProgram(message, cps);
16917 }
16918
16919 char *
16920 EngineDefinedVariant (ChessProgramState *cps, int n)
16921 {   // return name of n-th unknown variant that engine supports
16922     static char buf[MSG_SIZ];
16923     char *p, *s = cps->variants;
16924     if(!s) return NULL;
16925     do { // parse string from variants feature
16926       VariantClass v;
16927         p = strchr(s, ',');
16928         if(p) *p = NULLCHAR;
16929       v = StringToVariant(s);
16930       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16931         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16932             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16933                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16934                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16935                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16936             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16937         }
16938         if(p) *p++ = ',';
16939         if(n < 0) return buf;
16940     } while(s = p);
16941     return NULL;
16942 }
16943
16944 int
16945 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16946 {
16947   char buf[MSG_SIZ];
16948   int len = strlen(name);
16949   int val;
16950
16951   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16952     (*p) += len + 1;
16953     sscanf(*p, "%d", &val);
16954     *loc = (val != 0);
16955     while (**p && **p != ' ')
16956       (*p)++;
16957     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16958     SendToProgram(buf, cps);
16959     return TRUE;
16960   }
16961   return FALSE;
16962 }
16963
16964 int
16965 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16966 {
16967   char buf[MSG_SIZ];
16968   int len = strlen(name);
16969   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16970     (*p) += len + 1;
16971     sscanf(*p, "%d", loc);
16972     while (**p && **p != ' ') (*p)++;
16973     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16974     SendToProgram(buf, cps);
16975     return TRUE;
16976   }
16977   return FALSE;
16978 }
16979
16980 int
16981 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16982 {
16983   char buf[MSG_SIZ];
16984   int len = strlen(name);
16985   if (strncmp((*p), name, len) == 0
16986       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16987     (*p) += len + 2;
16988     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16989     sscanf(*p, "%[^\"]", *loc);
16990     while (**p && **p != '\"') (*p)++;
16991     if (**p == '\"') (*p)++;
16992     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16993     SendToProgram(buf, cps);
16994     return TRUE;
16995   }
16996   return FALSE;
16997 }
16998
16999 int
17000 ParseOption (Option *opt, ChessProgramState *cps)
17001 // [HGM] options: process the string that defines an engine option, and determine
17002 // name, type, default value, and allowed value range
17003 {
17004         char *p, *q, buf[MSG_SIZ];
17005         int n, min = (-1)<<31, max = 1<<31, def;
17006
17007         opt->target = &opt->value;   // OK for spin/slider and checkbox
17008         if(p = strstr(opt->name, " -spin ")) {
17009             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17010             if(max < min) max = min; // enforce consistency
17011             if(def < min) def = min;
17012             if(def > max) def = max;
17013             opt->value = def;
17014             opt->min = min;
17015             opt->max = max;
17016             opt->type = Spin;
17017         } else if((p = strstr(opt->name, " -slider "))) {
17018             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17019             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17020             if(max < min) max = min; // enforce consistency
17021             if(def < min) def = min;
17022             if(def > max) def = max;
17023             opt->value = def;
17024             opt->min = min;
17025             opt->max = max;
17026             opt->type = Spin; // Slider;
17027         } else if((p = strstr(opt->name, " -string "))) {
17028             opt->textValue = p+9;
17029             opt->type = TextBox;
17030             opt->target = &opt->textValue;
17031         } else if((p = strstr(opt->name, " -file "))) {
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 = FileName; // FileName;
17035             opt->target = &opt->textValue;
17036         } else if((p = strstr(opt->name, " -path "))) {
17037             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17038             opt->target = opt->textValue = p+7;
17039             opt->type = PathName; // PathName;
17040             opt->target = &opt->textValue;
17041         } else if(p = strstr(opt->name, " -check ")) {
17042             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17043             opt->value = (def != 0);
17044             opt->type = CheckBox;
17045         } else if(p = strstr(opt->name, " -combo ")) {
17046             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17047             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17048             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17049             opt->value = n = 0;
17050             while(q = StrStr(q, " /// ")) {
17051                 n++; *q = 0;    // count choices, and null-terminate each of them
17052                 q += 5;
17053                 if(*q == '*') { // remember default, which is marked with * prefix
17054                     q++;
17055                     opt->value = n;
17056                 }
17057                 cps->comboList[cps->comboCnt++] = q;
17058             }
17059             cps->comboList[cps->comboCnt++] = NULL;
17060             opt->max = n + 1;
17061             opt->type = ComboBox;
17062         } else if(p = strstr(opt->name, " -button")) {
17063             opt->type = Button;
17064         } else if(p = strstr(opt->name, " -save")) {
17065             opt->type = SaveButton;
17066         } else return FALSE;
17067         *p = 0; // terminate option name
17068         // now look if the command-line options define a setting for this engine option.
17069         if(cps->optionSettings && cps->optionSettings[0])
17070             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17071         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17072           snprintf(buf, MSG_SIZ, "option %s", p);
17073                 if(p = strstr(buf, ",")) *p = 0;
17074                 if(q = strchr(buf, '=')) switch(opt->type) {
17075                     case ComboBox:
17076                         for(n=0; n<opt->max; n++)
17077                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17078                         break;
17079                     case TextBox:
17080                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17081                         break;
17082                     case Spin:
17083                     case CheckBox:
17084                         opt->value = atoi(q+1);
17085                     default:
17086                         break;
17087                 }
17088                 strcat(buf, "\n");
17089                 SendToProgram(buf, cps);
17090         }
17091         return TRUE;
17092 }
17093
17094 void
17095 FeatureDone (ChessProgramState *cps, int val)
17096 {
17097   DelayedEventCallback cb = GetDelayedEvent();
17098   if ((cb == InitBackEnd3 && cps == &first) ||
17099       (cb == SettingsMenuIfReady && cps == &second) ||
17100       (cb == LoadEngine) ||
17101       (cb == TwoMachinesEventIfReady)) {
17102     CancelDelayedEvent();
17103     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17104   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17105   cps->initDone = val;
17106   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17107 }
17108
17109 /* Parse feature command from engine */
17110 void
17111 ParseFeatures (char *args, ChessProgramState *cps)
17112 {
17113   char *p = args;
17114   char *q = NULL;
17115   int val;
17116   char buf[MSG_SIZ];
17117
17118   for (;;) {
17119     while (*p == ' ') p++;
17120     if (*p == NULLCHAR) return;
17121
17122     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17123     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17124     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17125     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17126     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17127     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17128     if (BoolFeature(&p, "reuse", &val, cps)) {
17129       /* Engine can disable reuse, but can't enable it if user said no */
17130       if (!val) cps->reuse = FALSE;
17131       continue;
17132     }
17133     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17134     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17135       if (gameMode == TwoMachinesPlay) {
17136         DisplayTwoMachinesTitle();
17137       } else {
17138         DisplayTitle("");
17139       }
17140       continue;
17141     }
17142     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17143     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17144     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17145     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17146     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17147     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17148     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17149     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17150     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17151     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17152     if (IntFeature(&p, "done", &val, cps)) {
17153       FeatureDone(cps, val);
17154       continue;
17155     }
17156     /* Added by Tord: */
17157     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17158     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17159     /* End of additions by Tord */
17160
17161     /* [HGM] added features: */
17162     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17163     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17164     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17165     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17166     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17167     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17168     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17169     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17170         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17171         FREE(cps->option[cps->nrOptions].name);
17172         cps->option[cps->nrOptions].name = q; q = NULL;
17173         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17174           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17175             SendToProgram(buf, cps);
17176             continue;
17177         }
17178         if(cps->nrOptions >= MAX_OPTIONS) {
17179             cps->nrOptions--;
17180             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17181             DisplayError(buf, 0);
17182         }
17183         continue;
17184     }
17185     /* End of additions by HGM */
17186
17187     /* unknown feature: complain and skip */
17188     q = p;
17189     while (*q && *q != '=') q++;
17190     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17191     SendToProgram(buf, cps);
17192     p = q;
17193     if (*p == '=') {
17194       p++;
17195       if (*p == '\"') {
17196         p++;
17197         while (*p && *p != '\"') p++;
17198         if (*p == '\"') p++;
17199       } else {
17200         while (*p && *p != ' ') p++;
17201       }
17202     }
17203   }
17204
17205 }
17206
17207 void
17208 PeriodicUpdatesEvent (int newState)
17209 {
17210     if (newState == appData.periodicUpdates)
17211       return;
17212
17213     appData.periodicUpdates=newState;
17214
17215     /* Display type changes, so update it now */
17216 //    DisplayAnalysis();
17217
17218     /* Get the ball rolling again... */
17219     if (newState) {
17220         AnalysisPeriodicEvent(1);
17221         StartAnalysisClock();
17222     }
17223 }
17224
17225 void
17226 PonderNextMoveEvent (int newState)
17227 {
17228     if (newState == appData.ponderNextMove) return;
17229     if (gameMode == EditPosition) EditPositionDone(TRUE);
17230     if (newState) {
17231         SendToProgram("hard\n", &first);
17232         if (gameMode == TwoMachinesPlay) {
17233             SendToProgram("hard\n", &second);
17234         }
17235     } else {
17236         SendToProgram("easy\n", &first);
17237         thinkOutput[0] = NULLCHAR;
17238         if (gameMode == TwoMachinesPlay) {
17239             SendToProgram("easy\n", &second);
17240         }
17241     }
17242     appData.ponderNextMove = newState;
17243 }
17244
17245 void
17246 NewSettingEvent (int option, int *feature, char *command, int value)
17247 {
17248     char buf[MSG_SIZ];
17249
17250     if (gameMode == EditPosition) EditPositionDone(TRUE);
17251     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17252     if(feature == NULL || *feature) SendToProgram(buf, &first);
17253     if (gameMode == TwoMachinesPlay) {
17254         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17255     }
17256 }
17257
17258 void
17259 ShowThinkingEvent ()
17260 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17261 {
17262     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17263     int newState = appData.showThinking
17264         // [HGM] thinking: other features now need thinking output as well
17265         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17266
17267     if (oldState == newState) return;
17268     oldState = newState;
17269     if (gameMode == EditPosition) EditPositionDone(TRUE);
17270     if (oldState) {
17271         SendToProgram("post\n", &first);
17272         if (gameMode == TwoMachinesPlay) {
17273             SendToProgram("post\n", &second);
17274         }
17275     } else {
17276         SendToProgram("nopost\n", &first);
17277         thinkOutput[0] = NULLCHAR;
17278         if (gameMode == TwoMachinesPlay) {
17279             SendToProgram("nopost\n", &second);
17280         }
17281     }
17282 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17283 }
17284
17285 void
17286 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17287 {
17288   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17289   if (pr == NoProc) return;
17290   AskQuestion(title, question, replyPrefix, pr);
17291 }
17292
17293 void
17294 TypeInEvent (char firstChar)
17295 {
17296     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17297         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17298         gameMode == AnalyzeMode || gameMode == EditGame ||
17299         gameMode == EditPosition || gameMode == IcsExamining ||
17300         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17301         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17302                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17303                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17304         gameMode == Training) PopUpMoveDialog(firstChar);
17305 }
17306
17307 void
17308 TypeInDoneEvent (char *move)
17309 {
17310         Board board;
17311         int n, fromX, fromY, toX, toY;
17312         char promoChar;
17313         ChessMove moveType;
17314
17315         // [HGM] FENedit
17316         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17317                 EditPositionPasteFEN(move);
17318                 return;
17319         }
17320         // [HGM] movenum: allow move number to be typed in any mode
17321         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17322           ToNrEvent(2*n-1);
17323           return;
17324         }
17325         // undocumented kludge: allow command-line option to be typed in!
17326         // (potentially fatal, and does not implement the effect of the option.)
17327         // should only be used for options that are values on which future decisions will be made,
17328         // and definitely not on options that would be used during initialization.
17329         if(strstr(move, "!!! -") == move) {
17330             ParseArgsFromString(move+4);
17331             return;
17332         }
17333
17334       if (gameMode != EditGame && currentMove != forwardMostMove &&
17335         gameMode != Training) {
17336         DisplayMoveError(_("Displayed move is not current"));
17337       } else {
17338         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17339           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17340         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17341         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17342           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17343           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17344         } else {
17345           DisplayMoveError(_("Could not parse move"));
17346         }
17347       }
17348 }
17349
17350 void
17351 DisplayMove (int moveNumber)
17352 {
17353     char message[MSG_SIZ];
17354     char res[MSG_SIZ];
17355     char cpThinkOutput[MSG_SIZ];
17356
17357     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17358
17359     if (moveNumber == forwardMostMove - 1 ||
17360         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17361
17362         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17363
17364         if (strchr(cpThinkOutput, '\n')) {
17365             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17366         }
17367     } else {
17368         *cpThinkOutput = NULLCHAR;
17369     }
17370
17371     /* [AS] Hide thinking from human user */
17372     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17373         *cpThinkOutput = NULLCHAR;
17374         if( thinkOutput[0] != NULLCHAR ) {
17375             int i;
17376
17377             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17378                 cpThinkOutput[i] = '.';
17379             }
17380             cpThinkOutput[i] = NULLCHAR;
17381             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17382         }
17383     }
17384
17385     if (moveNumber == forwardMostMove - 1 &&
17386         gameInfo.resultDetails != NULL) {
17387         if (gameInfo.resultDetails[0] == NULLCHAR) {
17388           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17389         } else {
17390           snprintf(res, MSG_SIZ, " {%s} %s",
17391                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17392         }
17393     } else {
17394         res[0] = NULLCHAR;
17395     }
17396
17397     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17398         DisplayMessage(res, cpThinkOutput);
17399     } else {
17400       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17401                 WhiteOnMove(moveNumber) ? " " : ".. ",
17402                 parseList[moveNumber], res);
17403         DisplayMessage(message, cpThinkOutput);
17404     }
17405 }
17406
17407 void
17408 DisplayComment (int moveNumber, char *text)
17409 {
17410     char title[MSG_SIZ];
17411
17412     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17413       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17414     } else {
17415       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17416               WhiteOnMove(moveNumber) ? " " : ".. ",
17417               parseList[moveNumber]);
17418     }
17419     if (text != NULL && (appData.autoDisplayComment || commentUp))
17420         CommentPopUp(title, text);
17421 }
17422
17423 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17424  * might be busy thinking or pondering.  It can be omitted if your
17425  * gnuchess is configured to stop thinking immediately on any user
17426  * input.  However, that gnuchess feature depends on the FIONREAD
17427  * ioctl, which does not work properly on some flavors of Unix.
17428  */
17429 void
17430 Attention (ChessProgramState *cps)
17431 {
17432 #if ATTENTION
17433     if (!cps->useSigint) return;
17434     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17435     switch (gameMode) {
17436       case MachinePlaysWhite:
17437       case MachinePlaysBlack:
17438       case TwoMachinesPlay:
17439       case IcsPlayingWhite:
17440       case IcsPlayingBlack:
17441       case AnalyzeMode:
17442       case AnalyzeFile:
17443         /* Skip if we know it isn't thinking */
17444         if (!cps->maybeThinking) return;
17445         if (appData.debugMode)
17446           fprintf(debugFP, "Interrupting %s\n", cps->which);
17447         InterruptChildProcess(cps->pr);
17448         cps->maybeThinking = FALSE;
17449         break;
17450       default:
17451         break;
17452     }
17453 #endif /*ATTENTION*/
17454 }
17455
17456 int
17457 CheckFlags ()
17458 {
17459     if (whiteTimeRemaining <= 0) {
17460         if (!whiteFlag) {
17461             whiteFlag = TRUE;
17462             if (appData.icsActive) {
17463                 if (appData.autoCallFlag &&
17464                     gameMode == IcsPlayingBlack && !blackFlag) {
17465                   SendToICS(ics_prefix);
17466                   SendToICS("flag\n");
17467                 }
17468             } else {
17469                 if (blackFlag) {
17470                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17471                 } else {
17472                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17473                     if (appData.autoCallFlag) {
17474                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17475                         return TRUE;
17476                     }
17477                 }
17478             }
17479         }
17480     }
17481     if (blackTimeRemaining <= 0) {
17482         if (!blackFlag) {
17483             blackFlag = TRUE;
17484             if (appData.icsActive) {
17485                 if (appData.autoCallFlag &&
17486                     gameMode == IcsPlayingWhite && !whiteFlag) {
17487                   SendToICS(ics_prefix);
17488                   SendToICS("flag\n");
17489                 }
17490             } else {
17491                 if (whiteFlag) {
17492                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17493                 } else {
17494                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17495                     if (appData.autoCallFlag) {
17496                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17497                         return TRUE;
17498                     }
17499                 }
17500             }
17501         }
17502     }
17503     return FALSE;
17504 }
17505
17506 void
17507 CheckTimeControl ()
17508 {
17509     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17510         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17511
17512     /*
17513      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17514      */
17515     if ( !WhiteOnMove(forwardMostMove) ) {
17516         /* White made time control */
17517         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17518         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17519         /* [HGM] time odds: correct new time quota for time odds! */
17520                                             / WhitePlayer()->timeOdds;
17521         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17522     } else {
17523         lastBlack -= blackTimeRemaining;
17524         /* Black made time control */
17525         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17526                                             / WhitePlayer()->other->timeOdds;
17527         lastWhite = whiteTimeRemaining;
17528     }
17529 }
17530
17531 void
17532 DisplayBothClocks ()
17533 {
17534     int wom = gameMode == EditPosition ?
17535       !blackPlaysFirst : WhiteOnMove(currentMove);
17536     DisplayWhiteClock(whiteTimeRemaining, wom);
17537     DisplayBlackClock(blackTimeRemaining, !wom);
17538 }
17539
17540
17541 /* Timekeeping seems to be a portability nightmare.  I think everyone
17542    has ftime(), but I'm really not sure, so I'm including some ifdefs
17543    to use other calls if you don't.  Clocks will be less accurate if
17544    you have neither ftime nor gettimeofday.
17545 */
17546
17547 /* VS 2008 requires the #include outside of the function */
17548 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17549 #include <sys/timeb.h>
17550 #endif
17551
17552 /* Get the current time as a TimeMark */
17553 void
17554 GetTimeMark (TimeMark *tm)
17555 {
17556 #if HAVE_GETTIMEOFDAY
17557
17558     struct timeval timeVal;
17559     struct timezone timeZone;
17560
17561     gettimeofday(&timeVal, &timeZone);
17562     tm->sec = (long) timeVal.tv_sec;
17563     tm->ms = (int) (timeVal.tv_usec / 1000L);
17564
17565 #else /*!HAVE_GETTIMEOFDAY*/
17566 #if HAVE_FTIME
17567
17568 // include <sys/timeb.h> / moved to just above start of function
17569     struct timeb timeB;
17570
17571     ftime(&timeB);
17572     tm->sec = (long) timeB.time;
17573     tm->ms = (int) timeB.millitm;
17574
17575 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17576     tm->sec = (long) time(NULL);
17577     tm->ms = 0;
17578 #endif
17579 #endif
17580 }
17581
17582 /* Return the difference in milliseconds between two
17583    time marks.  We assume the difference will fit in a long!
17584 */
17585 long
17586 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17587 {
17588     return 1000L*(tm2->sec - tm1->sec) +
17589            (long) (tm2->ms - tm1->ms);
17590 }
17591
17592
17593 /*
17594  * Code to manage the game clocks.
17595  *
17596  * In tournament play, black starts the clock and then white makes a move.
17597  * We give the human user a slight advantage if he is playing white---the
17598  * clocks don't run until he makes his first move, so it takes zero time.
17599  * Also, we don't account for network lag, so we could get out of sync
17600  * with GNU Chess's clock -- but then, referees are always right.
17601  */
17602
17603 static TimeMark tickStartTM;
17604 static long intendedTickLength;
17605
17606 long
17607 NextTickLength (long timeRemaining)
17608 {
17609     long nominalTickLength, nextTickLength;
17610
17611     if (timeRemaining > 0L && timeRemaining <= 10000L)
17612       nominalTickLength = 100L;
17613     else
17614       nominalTickLength = 1000L;
17615     nextTickLength = timeRemaining % nominalTickLength;
17616     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17617
17618     return nextTickLength;
17619 }
17620
17621 /* Adjust clock one minute up or down */
17622 void
17623 AdjustClock (Boolean which, int dir)
17624 {
17625     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17626     if(which) blackTimeRemaining += 60000*dir;
17627     else      whiteTimeRemaining += 60000*dir;
17628     DisplayBothClocks();
17629     adjustedClock = TRUE;
17630 }
17631
17632 /* Stop clocks and reset to a fresh time control */
17633 void
17634 ResetClocks ()
17635 {
17636     (void) StopClockTimer();
17637     if (appData.icsActive) {
17638         whiteTimeRemaining = blackTimeRemaining = 0;
17639     } else if (searchTime) {
17640         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17641         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17642     } else { /* [HGM] correct new time quote for time odds */
17643         whiteTC = blackTC = fullTimeControlString;
17644         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17645         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17646     }
17647     if (whiteFlag || blackFlag) {
17648         DisplayTitle("");
17649         whiteFlag = blackFlag = FALSE;
17650     }
17651     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17652     DisplayBothClocks();
17653     adjustedClock = FALSE;
17654 }
17655
17656 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17657
17658 /* Decrement running clock by amount of time that has passed */
17659 void
17660 DecrementClocks ()
17661 {
17662     long timeRemaining;
17663     long lastTickLength, fudge;
17664     TimeMark now;
17665
17666     if (!appData.clockMode) return;
17667     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17668
17669     GetTimeMark(&now);
17670
17671     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17672
17673     /* Fudge if we woke up a little too soon */
17674     fudge = intendedTickLength - lastTickLength;
17675     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17676
17677     if (WhiteOnMove(forwardMostMove)) {
17678         if(whiteNPS >= 0) lastTickLength = 0;
17679         timeRemaining = whiteTimeRemaining -= lastTickLength;
17680         if(timeRemaining < 0 && !appData.icsActive) {
17681             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17682             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17683                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17684                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17685             }
17686         }
17687         DisplayWhiteClock(whiteTimeRemaining - fudge,
17688                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17689     } else {
17690         if(blackNPS >= 0) lastTickLength = 0;
17691         timeRemaining = blackTimeRemaining -= lastTickLength;
17692         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17693             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17694             if(suddenDeath) {
17695                 blackStartMove = forwardMostMove;
17696                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17697             }
17698         }
17699         DisplayBlackClock(blackTimeRemaining - fudge,
17700                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17701     }
17702     if (CheckFlags()) return;
17703
17704     if(twoBoards) { // count down secondary board's clocks as well
17705         activePartnerTime -= lastTickLength;
17706         partnerUp = 1;
17707         if(activePartner == 'W')
17708             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17709         else
17710             DisplayBlackClock(activePartnerTime, TRUE);
17711         partnerUp = 0;
17712     }
17713
17714     tickStartTM = now;
17715     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17716     StartClockTimer(intendedTickLength);
17717
17718     /* if the time remaining has fallen below the alarm threshold, sound the
17719      * alarm. if the alarm has sounded and (due to a takeback or time control
17720      * with increment) the time remaining has increased to a level above the
17721      * threshold, reset the alarm so it can sound again.
17722      */
17723
17724     if (appData.icsActive && appData.icsAlarm) {
17725
17726         /* make sure we are dealing with the user's clock */
17727         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17728                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17729            )) return;
17730
17731         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17732             alarmSounded = FALSE;
17733         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17734             PlayAlarmSound();
17735             alarmSounded = TRUE;
17736         }
17737     }
17738 }
17739
17740
17741 /* A player has just moved, so stop the previously running
17742    clock and (if in clock mode) start the other one.
17743    We redisplay both clocks in case we're in ICS mode, because
17744    ICS gives us an update to both clocks after every move.
17745    Note that this routine is called *after* forwardMostMove
17746    is updated, so the last fractional tick must be subtracted
17747    from the color that is *not* on move now.
17748 */
17749 void
17750 SwitchClocks (int newMoveNr)
17751 {
17752     long lastTickLength;
17753     TimeMark now;
17754     int flagged = FALSE;
17755
17756     GetTimeMark(&now);
17757
17758     if (StopClockTimer() && appData.clockMode) {
17759         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17760         if (!WhiteOnMove(forwardMostMove)) {
17761             if(blackNPS >= 0) lastTickLength = 0;
17762             blackTimeRemaining -= lastTickLength;
17763            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17764 //         if(pvInfoList[forwardMostMove].time == -1)
17765                  pvInfoList[forwardMostMove].time =               // use GUI time
17766                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17767         } else {
17768            if(whiteNPS >= 0) lastTickLength = 0;
17769            whiteTimeRemaining -= lastTickLength;
17770            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17771 //         if(pvInfoList[forwardMostMove].time == -1)
17772                  pvInfoList[forwardMostMove].time =
17773                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17774         }
17775         flagged = CheckFlags();
17776     }
17777     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17778     CheckTimeControl();
17779
17780     if (flagged || !appData.clockMode) return;
17781
17782     switch (gameMode) {
17783       case MachinePlaysBlack:
17784       case MachinePlaysWhite:
17785       case BeginningOfGame:
17786         if (pausing) return;
17787         break;
17788
17789       case EditGame:
17790       case PlayFromGameFile:
17791       case IcsExamining:
17792         return;
17793
17794       default:
17795         break;
17796     }
17797
17798     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17799         if(WhiteOnMove(forwardMostMove))
17800              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17801         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17802     }
17803
17804     tickStartTM = now;
17805     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17806       whiteTimeRemaining : blackTimeRemaining);
17807     StartClockTimer(intendedTickLength);
17808 }
17809
17810
17811 /* Stop both clocks */
17812 void
17813 StopClocks ()
17814 {
17815     long lastTickLength;
17816     TimeMark now;
17817
17818     if (!StopClockTimer()) return;
17819     if (!appData.clockMode) return;
17820
17821     GetTimeMark(&now);
17822
17823     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17824     if (WhiteOnMove(forwardMostMove)) {
17825         if(whiteNPS >= 0) lastTickLength = 0;
17826         whiteTimeRemaining -= lastTickLength;
17827         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17828     } else {
17829         if(blackNPS >= 0) lastTickLength = 0;
17830         blackTimeRemaining -= lastTickLength;
17831         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17832     }
17833     CheckFlags();
17834 }
17835
17836 /* Start clock of player on move.  Time may have been reset, so
17837    if clock is already running, stop and restart it. */
17838 void
17839 StartClocks ()
17840 {
17841     (void) StopClockTimer(); /* in case it was running already */
17842     DisplayBothClocks();
17843     if (CheckFlags()) return;
17844
17845     if (!appData.clockMode) return;
17846     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17847
17848     GetTimeMark(&tickStartTM);
17849     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17850       whiteTimeRemaining : blackTimeRemaining);
17851
17852    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17853     whiteNPS = blackNPS = -1;
17854     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17855        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17856         whiteNPS = first.nps;
17857     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17858        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17859         blackNPS = first.nps;
17860     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17861         whiteNPS = second.nps;
17862     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17863         blackNPS = second.nps;
17864     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17865
17866     StartClockTimer(intendedTickLength);
17867 }
17868
17869 char *
17870 TimeString (long ms)
17871 {
17872     long second, minute, hour, day;
17873     char *sign = "";
17874     static char buf[32];
17875
17876     if (ms > 0 && ms <= 9900) {
17877       /* convert milliseconds to tenths, rounding up */
17878       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17879
17880       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17881       return buf;
17882     }
17883
17884     /* convert milliseconds to seconds, rounding up */
17885     /* use floating point to avoid strangeness of integer division
17886        with negative dividends on many machines */
17887     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17888
17889     if (second < 0) {
17890         sign = "-";
17891         second = -second;
17892     }
17893
17894     day = second / (60 * 60 * 24);
17895     second = second % (60 * 60 * 24);
17896     hour = second / (60 * 60);
17897     second = second % (60 * 60);
17898     minute = second / 60;
17899     second = second % 60;
17900
17901     if (day > 0)
17902       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17903               sign, day, hour, minute, second);
17904     else if (hour > 0)
17905       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17906     else
17907       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17908
17909     return buf;
17910 }
17911
17912
17913 /*
17914  * This is necessary because some C libraries aren't ANSI C compliant yet.
17915  */
17916 char *
17917 StrStr (char *string, char *match)
17918 {
17919     int i, length;
17920
17921     length = strlen(match);
17922
17923     for (i = strlen(string) - length; i >= 0; i--, string++)
17924       if (!strncmp(match, string, length))
17925         return string;
17926
17927     return NULL;
17928 }
17929
17930 char *
17931 StrCaseStr (char *string, char *match)
17932 {
17933     int i, j, length;
17934
17935     length = strlen(match);
17936
17937     for (i = strlen(string) - length; i >= 0; i--, string++) {
17938         for (j = 0; j < length; j++) {
17939             if (ToLower(match[j]) != ToLower(string[j]))
17940               break;
17941         }
17942         if (j == length) return string;
17943     }
17944
17945     return NULL;
17946 }
17947
17948 #ifndef _amigados
17949 int
17950 StrCaseCmp (char *s1, char *s2)
17951 {
17952     char c1, c2;
17953
17954     for (;;) {
17955         c1 = ToLower(*s1++);
17956         c2 = ToLower(*s2++);
17957         if (c1 > c2) return 1;
17958         if (c1 < c2) return -1;
17959         if (c1 == NULLCHAR) return 0;
17960     }
17961 }
17962
17963
17964 int
17965 ToLower (int c)
17966 {
17967     return isupper(c) ? tolower(c) : c;
17968 }
17969
17970
17971 int
17972 ToUpper (int c)
17973 {
17974     return islower(c) ? toupper(c) : c;
17975 }
17976 #endif /* !_amigados    */
17977
17978 char *
17979 StrSave (char *s)
17980 {
17981   char *ret;
17982
17983   if ((ret = (char *) malloc(strlen(s) + 1)))
17984     {
17985       safeStrCpy(ret, s, strlen(s)+1);
17986     }
17987   return ret;
17988 }
17989
17990 char *
17991 StrSavePtr (char *s, char **savePtr)
17992 {
17993     if (*savePtr) {
17994         free(*savePtr);
17995     }
17996     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17997       safeStrCpy(*savePtr, s, strlen(s)+1);
17998     }
17999     return(*savePtr);
18000 }
18001
18002 char *
18003 PGNDate ()
18004 {
18005     time_t clock;
18006     struct tm *tm;
18007     char buf[MSG_SIZ];
18008
18009     clock = time((time_t *)NULL);
18010     tm = localtime(&clock);
18011     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18012             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18013     return StrSave(buf);
18014 }
18015
18016
18017 char *
18018 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18019 {
18020     int i, j, fromX, fromY, toX, toY;
18021     int whiteToPlay, haveRights = nrCastlingRights;
18022     char buf[MSG_SIZ];
18023     char *p, *q;
18024     int emptycount;
18025     ChessSquare piece;
18026
18027     whiteToPlay = (gameMode == EditPosition) ?
18028       !blackPlaysFirst : (move % 2 == 0);
18029     p = buf;
18030
18031     /* Piece placement data */
18032     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18033         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18034         emptycount = 0;
18035         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18036             if (boards[move][i][j] == EmptySquare) {
18037                 emptycount++;
18038             } else { ChessSquare piece = boards[move][i][j];
18039                 if (emptycount > 0) {
18040                     if(emptycount<10) /* [HGM] can be >= 10 */
18041                         *p++ = '0' + emptycount;
18042                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18043                     emptycount = 0;
18044                 }
18045                 if(PieceToChar(piece) == '+') {
18046                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18047                     *p++ = '+';
18048                     piece = (ChessSquare)(CHUDEMOTED(piece));
18049                 }
18050                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18051                 if(*p = PieceSuffix(piece)) p++;
18052                 if(p[-1] == '~') {
18053                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18054                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18055                     *p++ = '~';
18056                 }
18057             }
18058         }
18059         if (emptycount > 0) {
18060             if(emptycount<10) /* [HGM] can be >= 10 */
18061                 *p++ = '0' + emptycount;
18062             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18063             emptycount = 0;
18064         }
18065         *p++ = '/';
18066     }
18067     *(p - 1) = ' ';
18068
18069     /* [HGM] print Crazyhouse or Shogi holdings */
18070     if( gameInfo.holdingsWidth ) {
18071         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18072         q = p;
18073         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18074             piece = boards[move][i][BOARD_WIDTH-1];
18075             if( piece != EmptySquare )
18076               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18077                   *p++ = PieceToChar(piece);
18078         }
18079         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18080             piece = boards[move][BOARD_HEIGHT-i-1][0];
18081             if( piece != EmptySquare )
18082               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18083                   *p++ = PieceToChar(piece);
18084         }
18085
18086         if( q == p ) *p++ = '-';
18087         *p++ = ']';
18088         *p++ = ' ';
18089     }
18090
18091     /* Active color */
18092     *p++ = whiteToPlay ? 'w' : 'b';
18093     *p++ = ' ';
18094
18095   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18096     haveRights = 0; q = p;
18097     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18098       piece = boards[move][0][i];
18099       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18100         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18101       }
18102     }
18103     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18104       piece = boards[move][BOARD_HEIGHT-1][i];
18105       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18106         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18107       }
18108     }
18109     if(p == q) *p++ = '-';
18110     *p++ = ' ';
18111   }
18112
18113   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18114     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18115   } else {
18116   if(haveRights) {
18117      int handW=0, handB=0;
18118      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18119         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18120         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18121      }
18122      q = p;
18123      if(appData.fischerCastling) {
18124         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18125            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18126                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18127         } else {
18128        /* [HGM] write directly from rights */
18129            if(boards[move][CASTLING][2] != NoRights &&
18130               boards[move][CASTLING][0] != NoRights   )
18131                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18132            if(boards[move][CASTLING][2] != NoRights &&
18133               boards[move][CASTLING][1] != NoRights   )
18134                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18135         }
18136         if(handB) {
18137            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18138                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18139         } else {
18140            if(boards[move][CASTLING][5] != NoRights &&
18141               boards[move][CASTLING][3] != NoRights   )
18142                 *p++ = boards[move][CASTLING][3] + AAA;
18143            if(boards[move][CASTLING][5] != NoRights &&
18144               boards[move][CASTLING][4] != NoRights   )
18145                 *p++ = boards[move][CASTLING][4] + AAA;
18146         }
18147      } else {
18148
18149         /* [HGM] write true castling rights */
18150         if( nrCastlingRights == 6 ) {
18151             int q, k=0;
18152             if(boards[move][CASTLING][0] != NoRights &&
18153                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18154             q = (boards[move][CASTLING][1] != NoRights &&
18155                  boards[move][CASTLING][2] != NoRights  );
18156             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18157                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18158                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18159                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18160             }
18161             if(q) *p++ = 'Q';
18162             k = 0;
18163             if(boards[move][CASTLING][3] != NoRights &&
18164                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18165             q = (boards[move][CASTLING][4] != NoRights &&
18166                  boards[move][CASTLING][5] != NoRights  );
18167             if(handB) {
18168                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18169                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18170                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18171             }
18172             if(q) *p++ = 'q';
18173         }
18174      }
18175      if (q == p) *p++ = '-'; /* No castling rights */
18176      *p++ = ' ';
18177   }
18178
18179   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18180      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18181      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18182     /* En passant target square */
18183     if (move > backwardMostMove) {
18184         fromX = moveList[move - 1][0] - AAA;
18185         fromY = moveList[move - 1][1] - ONE;
18186         toX = moveList[move - 1][2] - AAA;
18187         toY = moveList[move - 1][3] - ONE;
18188         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18189             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18190             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18191             fromX == toX) {
18192             /* 2-square pawn move just happened */
18193             *p++ = toX + AAA;
18194             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18195         } else {
18196             *p++ = '-';
18197         }
18198     } else if(move == backwardMostMove) {
18199         // [HGM] perhaps we should always do it like this, and forget the above?
18200         if((signed char)boards[move][EP_STATUS] >= 0) {
18201             *p++ = boards[move][EP_STATUS] + AAA;
18202             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18203         } else {
18204             *p++ = '-';
18205         }
18206     } else {
18207         *p++ = '-';
18208     }
18209     *p++ = ' ';
18210   }
18211   }
18212
18213     if(moveCounts)
18214     {   int i = 0, j=move;
18215
18216         /* [HGM] find reversible plies */
18217         if (appData.debugMode) { int k;
18218             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18219             for(k=backwardMostMove; k<=forwardMostMove; k++)
18220                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18221
18222         }
18223
18224         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18225         if( j == backwardMostMove ) i += initialRulePlies;
18226         sprintf(p, "%d ", i);
18227         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18228
18229         /* Fullmove number */
18230         sprintf(p, "%d", (move / 2) + 1);
18231     } else *--p = NULLCHAR;
18232
18233     return StrSave(buf);
18234 }
18235
18236 Boolean
18237 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18238 {
18239     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18240     char *p, c;
18241     int emptycount, virgin[BOARD_FILES];
18242     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18243
18244     p = fen;
18245
18246     /* Piece placement data */
18247     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18248         j = 0;
18249         for (;;) {
18250             if (*p == '/' || *p == ' ' || *p == '[' ) {
18251                 if(j > w) w = j;
18252                 emptycount = gameInfo.boardWidth - j;
18253                 while (emptycount--)
18254                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18255                 if (*p == '/') p++;
18256                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18257                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18258                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18259                     }
18260                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18261                 }
18262                 break;
18263 #if(BOARD_FILES >= 10)*0
18264             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18265                 p++; emptycount=10;
18266                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18267                 while (emptycount--)
18268                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18269 #endif
18270             } else if (*p == '*') {
18271                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18272             } else if (isdigit(*p)) {
18273                 emptycount = *p++ - '0';
18274                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18275                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18276                 while (emptycount--)
18277                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18278             } else if (*p == '<') {
18279                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18280                 else if (i != 0 || !shuffle) return FALSE;
18281                 p++;
18282             } else if (shuffle && *p == '>') {
18283                 p++; // for now ignore closing shuffle range, and assume rank-end
18284             } else if (*p == '?') {
18285                 if (j >= gameInfo.boardWidth) return FALSE;
18286                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18287                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18288             } else if (*p == '+' || isalpha(*p)) {
18289                 char *q, *s = SUFFIXES;
18290                 if (j >= gameInfo.boardWidth) return FALSE;
18291                 if(*p=='+') {
18292                     char c = *++p;
18293                     if(q = strchr(s, p[1])) p++;
18294                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18295                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18296                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18297                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18298                 } else {
18299                     char c = *p++;
18300                     if(q = strchr(s, *p)) p++;
18301                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18302                 }
18303
18304                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18305                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18306                     piece = (ChessSquare) (PROMOTED(piece));
18307                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18308                     p++;
18309                 }
18310                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18311                 if(piece == king) wKingRank = i;
18312                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18313             } else {
18314                 return FALSE;
18315             }
18316         }
18317     }
18318     while (*p == '/' || *p == ' ') p++;
18319
18320     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18321
18322     /* [HGM] by default clear Crazyhouse holdings, if present */
18323     if(gameInfo.holdingsWidth) {
18324        for(i=0; i<BOARD_HEIGHT; i++) {
18325            board[i][0]             = EmptySquare; /* black holdings */
18326            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18327            board[i][1]             = (ChessSquare) 0; /* black counts */
18328            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18329        }
18330     }
18331
18332     /* [HGM] look for Crazyhouse holdings here */
18333     while(*p==' ') p++;
18334     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18335         int swap=0, wcnt=0, bcnt=0;
18336         if(*p == '[') p++;
18337         if(*p == '<') swap++, p++;
18338         if(*p == '-' ) p++; /* empty holdings */ else {
18339             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18340             /* if we would allow FEN reading to set board size, we would   */
18341             /* have to add holdings and shift the board read so far here   */
18342             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18343                 p++;
18344                 if((int) piece >= (int) BlackPawn ) {
18345                     i = (int)piece - (int)BlackPawn;
18346                     i = PieceToNumber((ChessSquare)i);
18347                     if( i >= gameInfo.holdingsSize ) return FALSE;
18348                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18349                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18350                     bcnt++;
18351                 } else {
18352                     i = (int)piece - (int)WhitePawn;
18353                     i = PieceToNumber((ChessSquare)i);
18354                     if( i >= gameInfo.holdingsSize ) return FALSE;
18355                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18356                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18357                     wcnt++;
18358                 }
18359             }
18360             if(subst) { // substitute back-rank question marks by holdings pieces
18361                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18362                     int k, m, n = bcnt + 1;
18363                     if(board[0][j] == ClearBoard) {
18364                         if(!wcnt) return FALSE;
18365                         n = rand() % wcnt;
18366                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18367                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18368                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18369                             break;
18370                         }
18371                     }
18372                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18373                         if(!bcnt) return FALSE;
18374                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18375                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18376                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18377                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18378                             break;
18379                         }
18380                     }
18381                 }
18382                 subst = 0;
18383             }
18384         }
18385         if(*p == ']') p++;
18386     }
18387
18388     if(subst) return FALSE; // substitution requested, but no holdings
18389
18390     while(*p == ' ') p++;
18391
18392     /* Active color */
18393     c = *p++;
18394     if(appData.colorNickNames) {
18395       if( c == appData.colorNickNames[0] ) c = 'w'; else
18396       if( c == appData.colorNickNames[1] ) c = 'b';
18397     }
18398     switch (c) {
18399       case 'w':
18400         *blackPlaysFirst = FALSE;
18401         break;
18402       case 'b':
18403         *blackPlaysFirst = TRUE;
18404         break;
18405       default:
18406         return FALSE;
18407     }
18408
18409     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18410     /* return the extra info in global variiables             */
18411
18412     while(*p==' ') p++;
18413
18414     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18415         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18416         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18417     }
18418
18419     /* set defaults in case FEN is incomplete */
18420     board[EP_STATUS] = EP_UNKNOWN;
18421     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18422     for(i=0; i<nrCastlingRights; i++ ) {
18423         board[CASTLING][i] =
18424             appData.fischerCastling ? NoRights : initialRights[i];
18425     }   /* assume possible unless obviously impossible */
18426     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18427     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18428     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18429                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18430     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18431     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18432     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18433                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18434     FENrulePlies = 0;
18435
18436     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18437       char *q = p;
18438       int w=0, b=0;
18439       while(isalpha(*p)) {
18440         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18441         if(islower(*p)) b |= 1 << (*p++ - 'a');
18442       }
18443       if(*p == '-') p++;
18444       if(p != q) {
18445         board[TOUCHED_W] = ~w;
18446         board[TOUCHED_B] = ~b;
18447         while(*p == ' ') p++;
18448       }
18449     } else
18450
18451     if(nrCastlingRights) {
18452       int fischer = 0;
18453       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18454       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18455           /* castling indicator present, so default becomes no castlings */
18456           for(i=0; i<nrCastlingRights; i++ ) {
18457                  board[CASTLING][i] = NoRights;
18458           }
18459       }
18460       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18461              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18462              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18463              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18464         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18465
18466         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18467             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18468             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18469         }
18470         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18471             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18472         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18473                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18474         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18475                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18476         switch(c) {
18477           case'K':
18478               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18479               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18480               board[CASTLING][2] = whiteKingFile;
18481               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18482               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18483               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18484               break;
18485           case'Q':
18486               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18487               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18488               board[CASTLING][2] = whiteKingFile;
18489               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18490               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18491               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18492               break;
18493           case'k':
18494               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18495               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18496               board[CASTLING][5] = blackKingFile;
18497               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18498               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18499               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18500               break;
18501           case'q':
18502               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18503               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18504               board[CASTLING][5] = blackKingFile;
18505               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18506               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18507               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18508           case '-':
18509               break;
18510           default: /* FRC castlings */
18511               if(c >= 'a') { /* black rights */
18512                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18513                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18514                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18515                   if(i == BOARD_RGHT) break;
18516                   board[CASTLING][5] = i;
18517                   c -= AAA;
18518                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18519                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18520                   if(c > i)
18521                       board[CASTLING][3] = c;
18522                   else
18523                       board[CASTLING][4] = c;
18524               } else { /* white rights */
18525                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18526                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18527                     if(board[0][i] == WhiteKing) break;
18528                   if(i == BOARD_RGHT) break;
18529                   board[CASTLING][2] = i;
18530                   c -= AAA - 'a' + 'A';
18531                   if(board[0][c] >= WhiteKing) break;
18532                   if(c > i)
18533                       board[CASTLING][0] = c;
18534                   else
18535                       board[CASTLING][1] = c;
18536               }
18537         }
18538       }
18539       for(i=0; i<nrCastlingRights; i++)
18540         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18541       if(gameInfo.variant == VariantSChess)
18542         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18543       if(fischer && shuffle) appData.fischerCastling = TRUE;
18544     if (appData.debugMode) {
18545         fprintf(debugFP, "FEN castling rights:");
18546         for(i=0; i<nrCastlingRights; i++)
18547         fprintf(debugFP, " %d", board[CASTLING][i]);
18548         fprintf(debugFP, "\n");
18549     }
18550
18551       while(*p==' ') p++;
18552     }
18553
18554     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18555
18556     /* read e.p. field in games that know e.p. capture */
18557     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18558        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18559        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18560       if(*p=='-') {
18561         p++; board[EP_STATUS] = EP_NONE;
18562       } else {
18563          char c = *p++ - AAA;
18564
18565          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18566          if(*p >= '0' && *p <='9') p++;
18567          board[EP_STATUS] = c;
18568       }
18569     }
18570
18571
18572     if(sscanf(p, "%d", &i) == 1) {
18573         FENrulePlies = i; /* 50-move ply counter */
18574         /* (The move number is still ignored)    */
18575     }
18576
18577     return TRUE;
18578 }
18579
18580 void
18581 EditPositionPasteFEN (char *fen)
18582 {
18583   if (fen != NULL) {
18584     Board initial_position;
18585
18586     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18587       DisplayError(_("Bad FEN position in clipboard"), 0);
18588       return ;
18589     } else {
18590       int savedBlackPlaysFirst = blackPlaysFirst;
18591       EditPositionEvent();
18592       blackPlaysFirst = savedBlackPlaysFirst;
18593       CopyBoard(boards[0], initial_position);
18594       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18595       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18596       DisplayBothClocks();
18597       DrawPosition(FALSE, boards[currentMove]);
18598     }
18599   }
18600 }
18601
18602 static char cseq[12] = "\\   ";
18603
18604 Boolean
18605 set_cont_sequence (char *new_seq)
18606 {
18607     int len;
18608     Boolean ret;
18609
18610     // handle bad attempts to set the sequence
18611         if (!new_seq)
18612                 return 0; // acceptable error - no debug
18613
18614     len = strlen(new_seq);
18615     ret = (len > 0) && (len < sizeof(cseq));
18616     if (ret)
18617       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18618     else if (appData.debugMode)
18619       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18620     return ret;
18621 }
18622
18623 /*
18624     reformat a source message so words don't cross the width boundary.  internal
18625     newlines are not removed.  returns the wrapped size (no null character unless
18626     included in source message).  If dest is NULL, only calculate the size required
18627     for the dest buffer.  lp argument indicats line position upon entry, and it's
18628     passed back upon exit.
18629 */
18630 int
18631 wrap (char *dest, char *src, int count, int width, int *lp)
18632 {
18633     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18634
18635     cseq_len = strlen(cseq);
18636     old_line = line = *lp;
18637     ansi = len = clen = 0;
18638
18639     for (i=0; i < count; i++)
18640     {
18641         if (src[i] == '\033')
18642             ansi = 1;
18643
18644         // if we hit the width, back up
18645         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18646         {
18647             // store i & len in case the word is too long
18648             old_i = i, old_len = len;
18649
18650             // find the end of the last word
18651             while (i && src[i] != ' ' && src[i] != '\n')
18652             {
18653                 i--;
18654                 len--;
18655             }
18656
18657             // word too long?  restore i & len before splitting it
18658             if ((old_i-i+clen) >= width)
18659             {
18660                 i = old_i;
18661                 len = old_len;
18662             }
18663
18664             // extra space?
18665             if (i && src[i-1] == ' ')
18666                 len--;
18667
18668             if (src[i] != ' ' && src[i] != '\n')
18669             {
18670                 i--;
18671                 if (len)
18672                     len--;
18673             }
18674
18675             // now append the newline and continuation sequence
18676             if (dest)
18677                 dest[len] = '\n';
18678             len++;
18679             if (dest)
18680                 strncpy(dest+len, cseq, cseq_len);
18681             len += cseq_len;
18682             line = cseq_len;
18683             clen = cseq_len;
18684             continue;
18685         }
18686
18687         if (dest)
18688             dest[len] = src[i];
18689         len++;
18690         if (!ansi)
18691             line++;
18692         if (src[i] == '\n')
18693             line = 0;
18694         if (src[i] == 'm')
18695             ansi = 0;
18696     }
18697     if (dest && appData.debugMode)
18698     {
18699         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18700             count, width, line, len, *lp);
18701         show_bytes(debugFP, src, count);
18702         fprintf(debugFP, "\ndest: ");
18703         show_bytes(debugFP, dest, len);
18704         fprintf(debugFP, "\n");
18705     }
18706     *lp = dest ? line : old_line;
18707
18708     return len;
18709 }
18710
18711 // [HGM] vari: routines for shelving variations
18712 Boolean modeRestore = FALSE;
18713
18714 void
18715 PushInner (int firstMove, int lastMove)
18716 {
18717         int i, j, nrMoves = lastMove - firstMove;
18718
18719         // push current tail of game on stack
18720         savedResult[storedGames] = gameInfo.result;
18721         savedDetails[storedGames] = gameInfo.resultDetails;
18722         gameInfo.resultDetails = NULL;
18723         savedFirst[storedGames] = firstMove;
18724         savedLast [storedGames] = lastMove;
18725         savedFramePtr[storedGames] = framePtr;
18726         framePtr -= nrMoves; // reserve space for the boards
18727         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18728             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18729             for(j=0; j<MOVE_LEN; j++)
18730                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18731             for(j=0; j<2*MOVE_LEN; j++)
18732                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18733             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18734             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18735             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18736             pvInfoList[firstMove+i-1].depth = 0;
18737             commentList[framePtr+i] = commentList[firstMove+i];
18738             commentList[firstMove+i] = NULL;
18739         }
18740
18741         storedGames++;
18742         forwardMostMove = firstMove; // truncate game so we can start variation
18743 }
18744
18745 void
18746 PushTail (int firstMove, int lastMove)
18747 {
18748         if(appData.icsActive) { // only in local mode
18749                 forwardMostMove = currentMove; // mimic old ICS behavior
18750                 return;
18751         }
18752         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18753
18754         PushInner(firstMove, lastMove);
18755         if(storedGames == 1) GreyRevert(FALSE);
18756         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18757 }
18758
18759 void
18760 PopInner (Boolean annotate)
18761 {
18762         int i, j, nrMoves;
18763         char buf[8000], moveBuf[20];
18764
18765         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18766         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18767         nrMoves = savedLast[storedGames] - currentMove;
18768         if(annotate) {
18769                 int cnt = 10;
18770                 if(!WhiteOnMove(currentMove))
18771                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18772                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18773                 for(i=currentMove; i<forwardMostMove; i++) {
18774                         if(WhiteOnMove(i))
18775                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18776                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18777                         strcat(buf, moveBuf);
18778                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18779                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18780                 }
18781                 strcat(buf, ")");
18782         }
18783         for(i=1; i<=nrMoves; i++) { // copy last variation back
18784             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18785             for(j=0; j<MOVE_LEN; j++)
18786                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18787             for(j=0; j<2*MOVE_LEN; j++)
18788                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18789             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18790             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18791             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18792             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18793             commentList[currentMove+i] = commentList[framePtr+i];
18794             commentList[framePtr+i] = NULL;
18795         }
18796         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18797         framePtr = savedFramePtr[storedGames];
18798         gameInfo.result = savedResult[storedGames];
18799         if(gameInfo.resultDetails != NULL) {
18800             free(gameInfo.resultDetails);
18801       }
18802         gameInfo.resultDetails = savedDetails[storedGames];
18803         forwardMostMove = currentMove + nrMoves;
18804 }
18805
18806 Boolean
18807 PopTail (Boolean annotate)
18808 {
18809         if(appData.icsActive) return FALSE; // only in local mode
18810         if(!storedGames) return FALSE; // sanity
18811         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18812
18813         PopInner(annotate);
18814         if(currentMove < forwardMostMove) ForwardEvent(); else
18815         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18816
18817         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18818         return TRUE;
18819 }
18820
18821 void
18822 CleanupTail ()
18823 {       // remove all shelved variations
18824         int i;
18825         for(i=0; i<storedGames; i++) {
18826             if(savedDetails[i])
18827                 free(savedDetails[i]);
18828             savedDetails[i] = NULL;
18829         }
18830         for(i=framePtr; i<MAX_MOVES; i++) {
18831                 if(commentList[i]) free(commentList[i]);
18832                 commentList[i] = NULL;
18833         }
18834         framePtr = MAX_MOVES-1;
18835         storedGames = 0;
18836 }
18837
18838 void
18839 LoadVariation (int index, char *text)
18840 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18841         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18842         int level = 0, move;
18843
18844         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18845         // first find outermost bracketing variation
18846         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18847             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18848                 if(*p == '{') wait = '}'; else
18849                 if(*p == '[') wait = ']'; else
18850                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18851                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18852             }
18853             if(*p == wait) wait = NULLCHAR; // closing ]} found
18854             p++;
18855         }
18856         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18857         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18858         end[1] = NULLCHAR; // clip off comment beyond variation
18859         ToNrEvent(currentMove-1);
18860         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18861         // kludge: use ParsePV() to append variation to game
18862         move = currentMove;
18863         ParsePV(start, TRUE, TRUE);
18864         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18865         ClearPremoveHighlights();
18866         CommentPopDown();
18867         ToNrEvent(currentMove+1);
18868 }
18869
18870 void
18871 LoadTheme ()
18872 {
18873     char *p, *q, buf[MSG_SIZ];
18874     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18875         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18876         ParseArgsFromString(buf);
18877         ActivateTheme(TRUE); // also redo colors
18878         return;
18879     }
18880     p = nickName;
18881     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18882     {
18883         int len;
18884         q = appData.themeNames;
18885         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18886       if(appData.useBitmaps) {
18887         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18888                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18889                 appData.liteBackTextureMode,
18890                 appData.darkBackTextureMode );
18891       } else {
18892         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18893                 Col2Text(2),   // lightSquareColor
18894                 Col2Text(3) ); // darkSquareColor
18895       }
18896       if(appData.useBorder) {
18897         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18898                 appData.border);
18899       } else {
18900         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18901       }
18902       if(appData.useFont) {
18903         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18904                 appData.renderPiecesWithFont,
18905                 appData.fontToPieceTable,
18906                 Col2Text(9),    // appData.fontBackColorWhite
18907                 Col2Text(10) ); // appData.fontForeColorBlack
18908       } else {
18909         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18910                 appData.pieceDirectory);
18911         if(!appData.pieceDirectory[0])
18912           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18913                 Col2Text(0),   // whitePieceColor
18914                 Col2Text(1) ); // blackPieceColor
18915       }
18916       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18917                 Col2Text(4),   // highlightSquareColor
18918                 Col2Text(5) ); // premoveHighlightColor
18919         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18920         if(insert != q) insert[-1] = NULLCHAR;
18921         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18922         if(q)   free(q);
18923     }
18924     ActivateTheme(FALSE);
18925 }