Use mouse wheel for selecting piece in Edit Position mode (XB)
[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         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
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) && !*engineVariant) 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 Wheel (int dir, int x, int y)
7985 {
7986     if(gameMode == EditPosition) {
7987         int xSqr = EventToSquare(x, BOARD_WIDTH);
7988         int ySqr = EventToSquare(y, BOARD_HEIGHT);
7989         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
7990         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
7991         do {
7992             boards[currentMove][ySqr][xSqr] += dir;
7993             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
7994             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
7995         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
7996         DrawPosition(FALSE, boards[currentMove]);
7997     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
7998 }
7999
8000 void
8001 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8002 {
8003 //    char * hint = lastHint;
8004     FrontEndProgramStats stats;
8005
8006     stats.which = cps == &first ? 0 : 1;
8007     stats.depth = cpstats->depth;
8008     stats.nodes = cpstats->nodes;
8009     stats.score = cpstats->score;
8010     stats.time = cpstats->time;
8011     stats.pv = cpstats->movelist;
8012     stats.hint = lastHint;
8013     stats.an_move_index = 0;
8014     stats.an_move_count = 0;
8015
8016     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8017         stats.hint = cpstats->move_name;
8018         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8019         stats.an_move_count = cpstats->nr_moves;
8020     }
8021
8022     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
8023
8024     SetProgramStats( &stats );
8025 }
8026
8027 void
8028 ClearEngineOutputPane (int which)
8029 {
8030     static FrontEndProgramStats dummyStats;
8031     dummyStats.which = which;
8032     dummyStats.pv = "#";
8033     SetProgramStats( &dummyStats );
8034 }
8035
8036 #define MAXPLAYERS 500
8037
8038 char *
8039 TourneyStandings (int display)
8040 {
8041     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8042     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8043     char result, *p, *names[MAXPLAYERS];
8044
8045     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8046         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8047     names[0] = p = strdup(appData.participants);
8048     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8049
8050     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8051
8052     while(result = appData.results[nr]) {
8053         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8054         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8055         wScore = bScore = 0;
8056         switch(result) {
8057           case '+': wScore = 2; break;
8058           case '-': bScore = 2; break;
8059           case '=': wScore = bScore = 1; break;
8060           case ' ':
8061           case '*': return strdup("busy"); // tourney not finished
8062         }
8063         score[w] += wScore;
8064         score[b] += bScore;
8065         games[w]++;
8066         games[b]++;
8067         nr++;
8068     }
8069     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8070     for(w=0; w<nPlayers; w++) {
8071         bScore = -1;
8072         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8073         ranking[w] = b; points[w] = bScore; score[b] = -2;
8074     }
8075     p = malloc(nPlayers*34+1);
8076     for(w=0; w<nPlayers && w<display; w++)
8077         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8078     free(names[0]);
8079     return p;
8080 }
8081
8082 void
8083 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8084 {       // count all piece types
8085         int p, f, r;
8086         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8087         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8088         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8089                 p = board[r][f];
8090                 pCnt[p]++;
8091                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8092                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8093                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8094                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8095                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8096                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8097         }
8098 }
8099
8100 int
8101 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8102 {
8103         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8104         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8105
8106         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8107         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8108         if(myPawns == 2 && nMine == 3) // KPP
8109             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8110         if(myPawns == 1 && nMine == 2) // KP
8111             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8112         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8113             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8114         if(myPawns) return FALSE;
8115         if(pCnt[WhiteRook+side])
8116             return pCnt[BlackRook-side] ||
8117                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8118                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8119                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8120         if(pCnt[WhiteCannon+side]) {
8121             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8122             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8123         }
8124         if(pCnt[WhiteKnight+side])
8125             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8126         return FALSE;
8127 }
8128
8129 int
8130 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8131 {
8132         VariantClass v = gameInfo.variant;
8133
8134         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8135         if(v == VariantShatranj) return TRUE; // always winnable through baring
8136         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8137         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8138
8139         if(v == VariantXiangqi) {
8140                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8141
8142                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8143                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8144                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8145                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8146                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8147                 if(stale) // we have at least one last-rank P plus perhaps C
8148                     return majors // KPKX
8149                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8150                 else // KCA*E*
8151                     return pCnt[WhiteFerz+side] // KCAK
8152                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8153                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8154                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8155
8156         } else if(v == VariantKnightmate) {
8157                 if(nMine == 1) return FALSE;
8158                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8159         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8160                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8161
8162                 if(nMine == 1) return FALSE; // bare King
8163                 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
8164                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8165                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8166                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8167                 if(pCnt[WhiteKnight+side])
8168                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8169                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8170                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8171                 if(nBishops)
8172                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8173                 if(pCnt[WhiteAlfil+side])
8174                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8175                 if(pCnt[WhiteWazir+side])
8176                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8177         }
8178
8179         return TRUE;
8180 }
8181
8182 int
8183 CompareWithRights (Board b1, Board b2)
8184 {
8185     int rights = 0;
8186     if(!CompareBoards(b1, b2)) return FALSE;
8187     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8188     /* compare castling rights */
8189     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8190            rights++; /* King lost rights, while rook still had them */
8191     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8192         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8193            rights++; /* but at least one rook lost them */
8194     }
8195     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8196            rights++;
8197     if( b1[CASTLING][5] != NoRights ) {
8198         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8199            rights++;
8200     }
8201     return rights == 0;
8202 }
8203
8204 int
8205 Adjudicate (ChessProgramState *cps)
8206 {       // [HGM] some adjudications useful with buggy engines
8207         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8208         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8209         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8210         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8211         int k, drop, count = 0; static int bare = 1;
8212         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8213         Boolean canAdjudicate = !appData.icsActive;
8214
8215         // most tests only when we understand the game, i.e. legality-checking on
8216             if( appData.testLegality )
8217             {   /* [HGM] Some more adjudications for obstinate engines */
8218                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8219                 static int moveCount = 6;
8220                 ChessMove result;
8221                 char *reason = NULL;
8222
8223                 /* Count what is on board. */
8224                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8225
8226                 /* Some material-based adjudications that have to be made before stalemate test */
8227                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8228                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8229                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8230                      if(canAdjudicate && appData.checkMates) {
8231                          if(engineOpponent)
8232                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8233                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8234                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8235                          return 1;
8236                      }
8237                 }
8238
8239                 /* Bare King in Shatranj (loses) or Losers (wins) */
8240                 if( nrW == 1 || nrB == 1) {
8241                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8242                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8243                      if(canAdjudicate && appData.checkMates) {
8244                          if(engineOpponent)
8245                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8246                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8247                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8248                          return 1;
8249                      }
8250                   } else
8251                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8252                   {    /* bare King */
8253                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8254                         if(canAdjudicate && appData.checkMates) {
8255                             /* but only adjudicate if adjudication enabled */
8256                             if(engineOpponent)
8257                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8258                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8259                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8260                             return 1;
8261                         }
8262                   }
8263                 } else bare = 1;
8264
8265
8266             // don't wait for engine to announce game end if we can judge ourselves
8267             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8268               case MT_CHECK:
8269                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8270                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8271                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8272                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8273                             checkCnt++;
8274                         if(checkCnt >= 2) {
8275                             reason = "Xboard adjudication: 3rd check";
8276                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8277                             break;
8278                         }
8279                     }
8280                 }
8281               case MT_NONE:
8282               default:
8283                 break;
8284               case MT_STEALMATE:
8285               case MT_STALEMATE:
8286               case MT_STAINMATE:
8287                 reason = "Xboard adjudication: Stalemate";
8288                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8289                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8290                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8291                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8292                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8293                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8294                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8295                                                                         EP_CHECKMATE : EP_WINS);
8296                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8297                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8298                 }
8299                 break;
8300               case MT_CHECKMATE:
8301                 reason = "Xboard adjudication: Checkmate";
8302                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8303                 if(gameInfo.variant == VariantShogi) {
8304                     if(forwardMostMove > backwardMostMove
8305                        && moveList[forwardMostMove-1][1] == '@'
8306                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8307                         reason = "XBoard adjudication: pawn-drop mate";
8308                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8309                     }
8310                 }
8311                 break;
8312             }
8313
8314                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8315                     case EP_STALEMATE:
8316                         result = GameIsDrawn; break;
8317                     case EP_CHECKMATE:
8318                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8319                     case EP_WINS:
8320                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8321                     default:
8322                         result = EndOfFile;
8323                 }
8324                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8325                     if(engineOpponent)
8326                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8327                     GameEnds( result, reason, GE_XBOARD );
8328                     return 1;
8329                 }
8330
8331                 /* Next absolutely insufficient mating material. */
8332                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8333                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8334                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8335
8336                      /* always flag draws, for judging claims */
8337                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8338
8339                      if(canAdjudicate && appData.materialDraws) {
8340                          /* but only adjudicate them if adjudication enabled */
8341                          if(engineOpponent) {
8342                            SendToProgram("force\n", engineOpponent); // suppress reply
8343                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8344                          }
8345                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8346                          return 1;
8347                      }
8348                 }
8349
8350                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8351                 if(gameInfo.variant == VariantXiangqi ?
8352                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8353                  : nrW + nrB == 4 &&
8354                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8355                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8356                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8357                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8358                    ) ) {
8359                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8360                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8361                           if(engineOpponent) {
8362                             SendToProgram("force\n", engineOpponent); // suppress reply
8363                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8364                           }
8365                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8366                           return 1;
8367                      }
8368                 } else moveCount = 6;
8369             }
8370
8371         // Repetition draws and 50-move rule can be applied independently of legality testing
8372
8373                 /* Check for rep-draws */
8374                 count = 0;
8375                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8376                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8377                 for(k = forwardMostMove-2;
8378                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8379                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8380                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8381                     k-=2)
8382                 {   int rights=0;
8383                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8384                         /* compare castling rights */
8385                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8386                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8387                                 rights++; /* King lost rights, while rook still had them */
8388                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8389                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8390                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8391                                    rights++; /* but at least one rook lost them */
8392                         }
8393                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8394                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8395                                 rights++;
8396                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8397                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8398                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8399                                    rights++;
8400                         }
8401                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8402                             && appData.drawRepeats > 1) {
8403                              /* adjudicate after user-specified nr of repeats */
8404                              int result = GameIsDrawn;
8405                              char *details = "XBoard adjudication: repetition draw";
8406                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8407                                 // [HGM] xiangqi: check for forbidden perpetuals
8408                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8409                                 for(m=forwardMostMove; m>k; m-=2) {
8410                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8411                                         ourPerpetual = 0; // the current mover did not always check
8412                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8413                                         hisPerpetual = 0; // the opponent did not always check
8414                                 }
8415                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8416                                                                         ourPerpetual, hisPerpetual);
8417                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8418                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8419                                     details = "Xboard adjudication: perpetual checking";
8420                                 } else
8421                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8422                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8423                                 } else
8424                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8425                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8426                                         result = BlackWins;
8427                                         details = "Xboard adjudication: repetition";
8428                                     }
8429                                 } else // it must be XQ
8430                                 // Now check for perpetual chases
8431                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8432                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8433                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8434                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8435                                         static char resdet[MSG_SIZ];
8436                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8437                                         details = resdet;
8438                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8439                                     } else
8440                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8441                                         break; // Abort repetition-checking loop.
8442                                 }
8443                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8444                              }
8445                              if(engineOpponent) {
8446                                SendToProgram("force\n", engineOpponent); // suppress reply
8447                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8448                              }
8449                              GameEnds( result, details, GE_XBOARD );
8450                              return 1;
8451                         }
8452                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8453                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8454                     }
8455                 }
8456
8457                 /* Now we test for 50-move draws. Determine ply count */
8458                 count = forwardMostMove;
8459                 /* look for last irreversble move */
8460                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8461                     count--;
8462                 /* if we hit starting position, add initial plies */
8463                 if( count == backwardMostMove )
8464                     count -= initialRulePlies;
8465                 count = forwardMostMove - count;
8466                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8467                         // adjust reversible move counter for checks in Xiangqi
8468                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8469                         if(i < backwardMostMove) i = backwardMostMove;
8470                         while(i <= forwardMostMove) {
8471                                 lastCheck = inCheck; // check evasion does not count
8472                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8473                                 if(inCheck || lastCheck) count--; // check does not count
8474                                 i++;
8475                         }
8476                 }
8477                 if( count >= 100)
8478                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8479                          /* this is used to judge if draw claims are legal */
8480                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8481                          if(engineOpponent) {
8482                            SendToProgram("force\n", engineOpponent); // suppress reply
8483                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8484                          }
8485                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8486                          return 1;
8487                 }
8488
8489                 /* if draw offer is pending, treat it as a draw claim
8490                  * when draw condition present, to allow engines a way to
8491                  * claim draws before making their move to avoid a race
8492                  * condition occurring after their move
8493                  */
8494                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8495                          char *p = NULL;
8496                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8497                              p = "Draw claim: 50-move rule";
8498                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8499                              p = "Draw claim: 3-fold repetition";
8500                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8501                              p = "Draw claim: insufficient mating material";
8502                          if( p != NULL && canAdjudicate) {
8503                              if(engineOpponent) {
8504                                SendToProgram("force\n", engineOpponent); // suppress reply
8505                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8506                              }
8507                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8508                              return 1;
8509                          }
8510                 }
8511
8512                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8513                     if(engineOpponent) {
8514                       SendToProgram("force\n", engineOpponent); // suppress reply
8515                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8516                     }
8517                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8518                     return 1;
8519                 }
8520         return 0;
8521 }
8522
8523 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8524 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8525 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8526
8527 static int
8528 BitbaseProbe ()
8529 {
8530     int pieces[10], squares[10], cnt=0, r, f, res;
8531     static int loaded;
8532     static PPROBE_EGBB probeBB;
8533     if(!appData.testLegality) return 10;
8534     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8535     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8536     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8537     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8538         ChessSquare piece = boards[forwardMostMove][r][f];
8539         int black = (piece >= BlackPawn);
8540         int type = piece - black*BlackPawn;
8541         if(piece == EmptySquare) continue;
8542         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8543         if(type == WhiteKing) type = WhiteQueen + 1;
8544         type = egbbCode[type];
8545         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8546         pieces[cnt] = type + black*6;
8547         if(++cnt > 5) return 11;
8548     }
8549     pieces[cnt] = squares[cnt] = 0;
8550     // probe EGBB
8551     if(loaded == 2) return 13; // loading failed before
8552     if(loaded == 0) {
8553         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8554         HMODULE lib;
8555         PLOAD_EGBB loadBB;
8556         loaded = 2; // prepare for failure
8557         if(!path) return 13; // no egbb installed
8558         strncpy(buf, path + 8, MSG_SIZ);
8559         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8560         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8561         lib = LoadLibrary(buf);
8562         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8563         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8564         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8565         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8566         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8567         loaded = 1; // success!
8568     }
8569     res = probeBB(forwardMostMove & 1, pieces, squares);
8570     return res > 0 ? 1 : res < 0 ? -1 : 0;
8571 }
8572
8573 char *
8574 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8575 {   // [HGM] book: this routine intercepts moves to simulate book replies
8576     char *bookHit = NULL;
8577
8578     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8579         char buf[MSG_SIZ];
8580         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8581         SendToProgram(buf, cps);
8582     }
8583     //first determine if the incoming move brings opponent into his book
8584     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8585         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8586     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8587     if(bookHit != NULL && !cps->bookSuspend) {
8588         // make sure opponent is not going to reply after receiving move to book position
8589         SendToProgram("force\n", cps);
8590         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8591     }
8592     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8593     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8594     // now arrange restart after book miss
8595     if(bookHit) {
8596         // after a book hit we never send 'go', and the code after the call to this routine
8597         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8598         char buf[MSG_SIZ], *move = bookHit;
8599         if(cps->useSAN) {
8600             int fromX, fromY, toX, toY;
8601             char promoChar;
8602             ChessMove moveType;
8603             move = buf + 30;
8604             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8605                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8606                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8607                                     PosFlags(forwardMostMove),
8608                                     fromY, fromX, toY, toX, promoChar, move);
8609             } else {
8610                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8611                 bookHit = NULL;
8612             }
8613         }
8614         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8615         SendToProgram(buf, cps);
8616         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8617     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8618         SendToProgram("go\n", cps);
8619         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8620     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8621         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8622             SendToProgram("go\n", cps);
8623         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8624     }
8625     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8626 }
8627
8628 int
8629 LoadError (char *errmess, ChessProgramState *cps)
8630 {   // unloads engine and switches back to -ncp mode if it was first
8631     if(cps->initDone) return FALSE;
8632     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8633     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8634     cps->pr = NoProc;
8635     if(cps == &first) {
8636         appData.noChessProgram = TRUE;
8637         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8638         gameMode = BeginningOfGame; ModeHighlight();
8639         SetNCPMode();
8640     }
8641     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8642     DisplayMessage("", ""); // erase waiting message
8643     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8644     return TRUE;
8645 }
8646
8647 char *savedMessage;
8648 ChessProgramState *savedState;
8649 void
8650 DeferredBookMove (void)
8651 {
8652         if(savedState->lastPing != savedState->lastPong)
8653                     ScheduleDelayedEvent(DeferredBookMove, 10);
8654         else
8655         HandleMachineMove(savedMessage, savedState);
8656 }
8657
8658 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8659 static ChessProgramState *stalledEngine;
8660 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8661
8662 void
8663 HandleMachineMove (char *message, ChessProgramState *cps)
8664 {
8665     static char firstLeg[20];
8666     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8667     char realname[MSG_SIZ];
8668     int fromX, fromY, toX, toY;
8669     ChessMove moveType;
8670     char promoChar, roar;
8671     char *p, *pv=buf1;
8672     int oldError;
8673     char *bookHit;
8674
8675     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8676         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8677         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8678             DisplayError(_("Invalid pairing from pairing engine"), 0);
8679             return;
8680         }
8681         pairingReceived = 1;
8682         NextMatchGame();
8683         return; // Skim the pairing messages here.
8684     }
8685
8686     oldError = cps->userError; cps->userError = 0;
8687
8688 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8689     /*
8690      * Kludge to ignore BEL characters
8691      */
8692     while (*message == '\007') message++;
8693
8694     /*
8695      * [HGM] engine debug message: ignore lines starting with '#' character
8696      */
8697     if(cps->debug && *message == '#') return;
8698
8699     /*
8700      * Look for book output
8701      */
8702     if (cps == &first && bookRequested) {
8703         if (message[0] == '\t' || message[0] == ' ') {
8704             /* Part of the book output is here; append it */
8705             strcat(bookOutput, message);
8706             strcat(bookOutput, "  \n");
8707             return;
8708         } else if (bookOutput[0] != NULLCHAR) {
8709             /* All of book output has arrived; display it */
8710             char *p = bookOutput;
8711             while (*p != NULLCHAR) {
8712                 if (*p == '\t') *p = ' ';
8713                 p++;
8714             }
8715             DisplayInformation(bookOutput);
8716             bookRequested = FALSE;
8717             /* Fall through to parse the current output */
8718         }
8719     }
8720
8721     /*
8722      * Look for machine move.
8723      */
8724     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8725         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8726     {
8727         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8728             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8729             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8730             stalledEngine = cps;
8731             if(appData.ponderNextMove) { // bring opponent out of ponder
8732                 if(gameMode == TwoMachinesPlay) {
8733                     if(cps->other->pause)
8734                         PauseEngine(cps->other);
8735                     else
8736                         SendToProgram("easy\n", cps->other);
8737                 }
8738             }
8739             StopClocks();
8740             return;
8741         }
8742
8743       if(cps->usePing) {
8744
8745         /* This method is only useful on engines that support ping */
8746         if(abortEngineThink) {
8747             if (appData.debugMode) {
8748                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8749             }
8750             SendToProgram("undo\n", cps);
8751             return;
8752         }
8753
8754         if (cps->lastPing != cps->lastPong) {
8755             /* Extra move from before last new; ignore */
8756             if (appData.debugMode) {
8757                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8758             }
8759           return;
8760         }
8761
8762       } else {
8763
8764         int machineWhite = FALSE;
8765
8766         switch (gameMode) {
8767           case BeginningOfGame:
8768             /* Extra move from before last reset; ignore */
8769             if (appData.debugMode) {
8770                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8771             }
8772             return;
8773
8774           case EndOfGame:
8775           case IcsIdle:
8776           default:
8777             /* Extra move after we tried to stop.  The mode test is
8778                not a reliable way of detecting this problem, but it's
8779                the best we can do on engines that don't support ping.
8780             */
8781             if (appData.debugMode) {
8782                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8783                         cps->which, gameMode);
8784             }
8785             SendToProgram("undo\n", cps);
8786             return;
8787
8788           case MachinePlaysWhite:
8789           case IcsPlayingWhite:
8790             machineWhite = TRUE;
8791             break;
8792
8793           case MachinePlaysBlack:
8794           case IcsPlayingBlack:
8795             machineWhite = FALSE;
8796             break;
8797
8798           case TwoMachinesPlay:
8799             machineWhite = (cps->twoMachinesColor[0] == 'w');
8800             break;
8801         }
8802         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8803             if (appData.debugMode) {
8804                 fprintf(debugFP,
8805                         "Ignoring move out of turn by %s, gameMode %d"
8806                         ", forwardMost %d\n",
8807                         cps->which, gameMode, forwardMostMove);
8808             }
8809             return;
8810         }
8811       }
8812
8813         if(cps->alphaRank) AlphaRank(machineMove, 4);
8814
8815         // [HGM] lion: (some very limited) support for Alien protocol
8816         killX = killY = kill2X = kill2Y = -1;
8817         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8818             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8819             return;
8820         }
8821         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8822             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8823             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8824         }
8825         if(firstLeg[0]) { // there was a previous leg;
8826             // only support case where same piece makes two step
8827             char buf[20], *p = machineMove+1, *q = buf+1, f;
8828             safeStrCpy(buf, machineMove, 20);
8829             while(isdigit(*q)) q++; // find start of to-square
8830             safeStrCpy(machineMove, firstLeg, 20);
8831             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8832             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8833             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8834             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8835             firstLeg[0] = NULLCHAR;
8836         }
8837
8838         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8839                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8840             /* Machine move could not be parsed; ignore it. */
8841           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8842                     machineMove, _(cps->which));
8843             DisplayMoveError(buf1);
8844             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8845                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8846             if (gameMode == TwoMachinesPlay) {
8847               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8848                        buf1, GE_XBOARD);
8849             }
8850             return;
8851         }
8852
8853         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8854         /* So we have to redo legality test with true e.p. status here,  */
8855         /* to make sure an illegal e.p. capture does not slip through,   */
8856         /* to cause a forfeit on a justified illegal-move complaint      */
8857         /* of the opponent.                                              */
8858         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8859            ChessMove moveType;
8860            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8861                              fromY, fromX, toY, toX, promoChar);
8862             if(moveType == IllegalMove) {
8863               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8864                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8865                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8866                            buf1, GE_XBOARD);
8867                 return;
8868            } else if(!appData.fischerCastling)
8869            /* [HGM] Kludge to handle engines that send FRC-style castling
8870               when they shouldn't (like TSCP-Gothic) */
8871            switch(moveType) {
8872              case WhiteASideCastleFR:
8873              case BlackASideCastleFR:
8874                toX+=2;
8875                currentMoveString[2]++;
8876                break;
8877              case WhiteHSideCastleFR:
8878              case BlackHSideCastleFR:
8879                toX--;
8880                currentMoveString[2]--;
8881                break;
8882              default: ; // nothing to do, but suppresses warning of pedantic compilers
8883            }
8884         }
8885         hintRequested = FALSE;
8886         lastHint[0] = NULLCHAR;
8887         bookRequested = FALSE;
8888         /* Program may be pondering now */
8889         cps->maybeThinking = TRUE;
8890         if (cps->sendTime == 2) cps->sendTime = 1;
8891         if (cps->offeredDraw) cps->offeredDraw--;
8892
8893         /* [AS] Save move info*/
8894         pvInfoList[ forwardMostMove ].score = programStats.score;
8895         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8896         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8897
8898         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8899
8900         /* Test suites abort the 'game' after one move */
8901         if(*appData.finger) {
8902            static FILE *f;
8903            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8904            if(!f) f = fopen(appData.finger, "w");
8905            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8906            else { DisplayFatalError("Bad output file", errno, 0); return; }
8907            free(fen);
8908            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8909         }
8910         if(appData.epd) {
8911            if(solvingTime >= 0) {
8912               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8913               totalTime += solvingTime; first.matchWins++;
8914            } else {
8915               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8916               second.matchWins++;
8917            }
8918            OutputKibitz(2, buf1);
8919            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8920         }
8921
8922         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8923         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8924             int count = 0;
8925
8926             while( count < adjudicateLossPlies ) {
8927                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8928
8929                 if( count & 1 ) {
8930                     score = -score; /* Flip score for winning side */
8931                 }
8932
8933                 if( score > appData.adjudicateLossThreshold ) {
8934                     break;
8935                 }
8936
8937                 count++;
8938             }
8939
8940             if( count >= adjudicateLossPlies ) {
8941                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8942
8943                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8944                     "Xboard adjudication",
8945                     GE_XBOARD );
8946
8947                 return;
8948             }
8949         }
8950
8951         if(Adjudicate(cps)) {
8952             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8953             return; // [HGM] adjudicate: for all automatic game ends
8954         }
8955
8956 #if ZIPPY
8957         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8958             first.initDone) {
8959           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8960                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8961                 SendToICS("draw ");
8962                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8963           }
8964           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8965           ics_user_moved = 1;
8966           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8967                 char buf[3*MSG_SIZ];
8968
8969                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8970                         programStats.score / 100.,
8971                         programStats.depth,
8972                         programStats.time / 100.,
8973                         (unsigned int)programStats.nodes,
8974                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8975                         programStats.movelist);
8976                 SendToICS(buf);
8977           }
8978         }
8979 #endif
8980
8981         /* [AS] Clear stats for next move */
8982         ClearProgramStats();
8983         thinkOutput[0] = NULLCHAR;
8984         hiddenThinkOutputState = 0;
8985
8986         bookHit = NULL;
8987         if (gameMode == TwoMachinesPlay) {
8988             /* [HGM] relaying draw offers moved to after reception of move */
8989             /* and interpreting offer as claim if it brings draw condition */
8990             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8991                 SendToProgram("draw\n", cps->other);
8992             }
8993             if (cps->other->sendTime) {
8994                 SendTimeRemaining(cps->other,
8995                                   cps->other->twoMachinesColor[0] == 'w');
8996             }
8997             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8998             if (firstMove && !bookHit) {
8999                 firstMove = FALSE;
9000                 if (cps->other->useColors) {
9001                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9002                 }
9003                 SendToProgram("go\n", cps->other);
9004             }
9005             cps->other->maybeThinking = TRUE;
9006         }
9007
9008         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9009
9010         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9011
9012         if (!pausing && appData.ringBellAfterMoves) {
9013             if(!roar) RingBell();
9014         }
9015
9016         /*
9017          * Reenable menu items that were disabled while
9018          * machine was thinking
9019          */
9020         if (gameMode != TwoMachinesPlay)
9021             SetUserThinkingEnables();
9022
9023         // [HGM] book: after book hit opponent has received move and is now in force mode
9024         // force the book reply into it, and then fake that it outputted this move by jumping
9025         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9026         if(bookHit) {
9027                 static char bookMove[MSG_SIZ]; // a bit generous?
9028
9029                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9030                 strcat(bookMove, bookHit);
9031                 message = bookMove;
9032                 cps = cps->other;
9033                 programStats.nodes = programStats.depth = programStats.time =
9034                 programStats.score = programStats.got_only_move = 0;
9035                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9036
9037                 if(cps->lastPing != cps->lastPong) {
9038                     savedMessage = message; // args for deferred call
9039                     savedState = cps;
9040                     ScheduleDelayedEvent(DeferredBookMove, 10);
9041                     return;
9042                 }
9043                 goto FakeBookMove;
9044         }
9045
9046         return;
9047     }
9048
9049     /* Set special modes for chess engines.  Later something general
9050      *  could be added here; for now there is just one kludge feature,
9051      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9052      *  when "xboard" is given as an interactive command.
9053      */
9054     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9055         cps->useSigint = FALSE;
9056         cps->useSigterm = FALSE;
9057     }
9058     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9059       ParseFeatures(message+8, cps);
9060       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9061     }
9062
9063     if (!strncmp(message, "setup ", 6) && 
9064         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9065           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9066                                         ) { // [HGM] allow first engine to define opening position
9067       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9068       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9069       *buf = NULLCHAR;
9070       if(sscanf(message, "setup (%s", buf) == 1) {
9071         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9072         ASSIGN(appData.pieceToCharTable, buf);
9073       }
9074       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9075       if(dummy >= 3) {
9076         while(message[s] && message[s++] != ' ');
9077         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9078            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9079             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9080             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9081           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9082           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9083           startedFromSetupPosition = FALSE;
9084         }
9085       }
9086       if(startedFromSetupPosition) return;
9087       ParseFEN(boards[0], &dummy, message+s, FALSE);
9088       DrawPosition(TRUE, boards[0]);
9089       CopyBoard(initialPosition, boards[0]);
9090       startedFromSetupPosition = TRUE;
9091       return;
9092     }
9093     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9094       ChessSquare piece = WhitePawn;
9095       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9096       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9097       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9098       piece += CharToPiece(ID & 255) - WhitePawn;
9099       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9100       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9101       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9102       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9103       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9104       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9105                                                && gameInfo.variant != VariantGreat
9106                                                && gameInfo.variant != VariantFairy    ) return;
9107       if(piece < EmptySquare) {
9108         pieceDefs = TRUE;
9109         ASSIGN(pieceDesc[piece], buf1);
9110         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9111       }
9112       return;
9113     }
9114     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9115       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9116       Sweep(0);
9117       return;
9118     }
9119     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9120      * want this, I was asked to put it in, and obliged.
9121      */
9122     if (!strncmp(message, "setboard ", 9)) {
9123         Board initial_position;
9124
9125         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9126
9127         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9128             DisplayError(_("Bad FEN received from engine"), 0);
9129             return ;
9130         } else {
9131            Reset(TRUE, FALSE);
9132            CopyBoard(boards[0], initial_position);
9133            initialRulePlies = FENrulePlies;
9134            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9135            else gameMode = MachinePlaysBlack;
9136            DrawPosition(FALSE, boards[currentMove]);
9137         }
9138         return;
9139     }
9140
9141     /*
9142      * Look for communication commands
9143      */
9144     if (!strncmp(message, "telluser ", 9)) {
9145         if(message[9] == '\\' && message[10] == '\\')
9146             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9147         PlayTellSound();
9148         DisplayNote(message + 9);
9149         return;
9150     }
9151     if (!strncmp(message, "tellusererror ", 14)) {
9152         cps->userError = 1;
9153         if(message[14] == '\\' && message[15] == '\\')
9154             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9155         PlayTellSound();
9156         DisplayError(message + 14, 0);
9157         return;
9158     }
9159     if (!strncmp(message, "tellopponent ", 13)) {
9160       if (appData.icsActive) {
9161         if (loggedOn) {
9162           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9163           SendToICS(buf1);
9164         }
9165       } else {
9166         DisplayNote(message + 13);
9167       }
9168       return;
9169     }
9170     if (!strncmp(message, "tellothers ", 11)) {
9171       if (appData.icsActive) {
9172         if (loggedOn) {
9173           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9174           SendToICS(buf1);
9175         }
9176       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9177       return;
9178     }
9179     if (!strncmp(message, "tellall ", 8)) {
9180       if (appData.icsActive) {
9181         if (loggedOn) {
9182           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9183           SendToICS(buf1);
9184         }
9185       } else {
9186         DisplayNote(message + 8);
9187       }
9188       return;
9189     }
9190     if (strncmp(message, "warning", 7) == 0) {
9191         /* Undocumented feature, use tellusererror in new code */
9192         DisplayError(message, 0);
9193         return;
9194     }
9195     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9196         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9197         strcat(realname, " query");
9198         AskQuestion(realname, buf2, buf1, cps->pr);
9199         return;
9200     }
9201     /* Commands from the engine directly to ICS.  We don't allow these to be
9202      *  sent until we are logged on. Crafty kibitzes have been known to
9203      *  interfere with the login process.
9204      */
9205     if (loggedOn) {
9206         if (!strncmp(message, "tellics ", 8)) {
9207             SendToICS(message + 8);
9208             SendToICS("\n");
9209             return;
9210         }
9211         if (!strncmp(message, "tellicsnoalias ", 15)) {
9212             SendToICS(ics_prefix);
9213             SendToICS(message + 15);
9214             SendToICS("\n");
9215             return;
9216         }
9217         /* The following are for backward compatibility only */
9218         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9219             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9220             SendToICS(ics_prefix);
9221             SendToICS(message);
9222             SendToICS("\n");
9223             return;
9224         }
9225     }
9226     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9227         if(initPing == cps->lastPong) {
9228             if(gameInfo.variant == VariantUnknown) {
9229                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9230                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9231                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9232             }
9233             initPing = -1;
9234         }
9235         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9236             abortEngineThink = FALSE;
9237             DisplayMessage("", "");
9238             ThawUI();
9239         }
9240         return;
9241     }
9242     if(!strncmp(message, "highlight ", 10)) {
9243         if(appData.testLegality && !*engineVariant && appData.markers) return;
9244         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9245         return;
9246     }
9247     if(!strncmp(message, "click ", 6)) {
9248         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9249         if(appData.testLegality || !appData.oneClick) return;
9250         sscanf(message+6, "%c%d%c", &f, &y, &c);
9251         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9252         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9253         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9254         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9255         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9256         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9257             LeftClick(Release, lastLeftX, lastLeftY);
9258         controlKey  = (c == ',');
9259         LeftClick(Press, x, y);
9260         LeftClick(Release, x, y);
9261         first.highlight = f;
9262         return;
9263     }
9264     /*
9265      * If the move is illegal, cancel it and redraw the board.
9266      * Also deal with other error cases.  Matching is rather loose
9267      * here to accommodate engines written before the spec.
9268      */
9269     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9270         strncmp(message, "Error", 5) == 0) {
9271         if (StrStr(message, "name") ||
9272             StrStr(message, "rating") || StrStr(message, "?") ||
9273             StrStr(message, "result") || StrStr(message, "board") ||
9274             StrStr(message, "bk") || StrStr(message, "computer") ||
9275             StrStr(message, "variant") || StrStr(message, "hint") ||
9276             StrStr(message, "random") || StrStr(message, "depth") ||
9277             StrStr(message, "accepted")) {
9278             return;
9279         }
9280         if (StrStr(message, "protover")) {
9281           /* Program is responding to input, so it's apparently done
9282              initializing, and this error message indicates it is
9283              protocol version 1.  So we don't need to wait any longer
9284              for it to initialize and send feature commands. */
9285           FeatureDone(cps, 1);
9286           cps->protocolVersion = 1;
9287           return;
9288         }
9289         cps->maybeThinking = FALSE;
9290
9291         if (StrStr(message, "draw")) {
9292             /* Program doesn't have "draw" command */
9293             cps->sendDrawOffers = 0;
9294             return;
9295         }
9296         if (cps->sendTime != 1 &&
9297             (StrStr(message, "time") || StrStr(message, "otim"))) {
9298           /* Program apparently doesn't have "time" or "otim" command */
9299           cps->sendTime = 0;
9300           return;
9301         }
9302         if (StrStr(message, "analyze")) {
9303             cps->analysisSupport = FALSE;
9304             cps->analyzing = FALSE;
9305 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9306             EditGameEvent(); // [HGM] try to preserve loaded game
9307             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9308             DisplayError(buf2, 0);
9309             return;
9310         }
9311         if (StrStr(message, "(no matching move)st")) {
9312           /* Special kludge for GNU Chess 4 only */
9313           cps->stKludge = TRUE;
9314           SendTimeControl(cps, movesPerSession, timeControl,
9315                           timeIncrement, appData.searchDepth,
9316                           searchTime);
9317           return;
9318         }
9319         if (StrStr(message, "(no matching move)sd")) {
9320           /* Special kludge for GNU Chess 4 only */
9321           cps->sdKludge = TRUE;
9322           SendTimeControl(cps, movesPerSession, timeControl,
9323                           timeIncrement, appData.searchDepth,
9324                           searchTime);
9325           return;
9326         }
9327         if (!StrStr(message, "llegal")) {
9328             return;
9329         }
9330         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9331             gameMode == IcsIdle) return;
9332         if (forwardMostMove <= backwardMostMove) return;
9333         if (pausing) PauseEvent();
9334       if(appData.forceIllegal) {
9335             // [HGM] illegal: machine refused move; force position after move into it
9336           SendToProgram("force\n", cps);
9337           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9338                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9339                 // when black is to move, while there might be nothing on a2 or black
9340                 // might already have the move. So send the board as if white has the move.
9341                 // But first we must change the stm of the engine, as it refused the last move
9342                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9343                 if(WhiteOnMove(forwardMostMove)) {
9344                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9345                     SendBoard(cps, forwardMostMove); // kludgeless board
9346                 } else {
9347                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9348                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9349                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9350                 }
9351           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9352             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9353                  gameMode == TwoMachinesPlay)
9354               SendToProgram("go\n", cps);
9355             return;
9356       } else
9357         if (gameMode == PlayFromGameFile) {
9358             /* Stop reading this game file */
9359             gameMode = EditGame;
9360             ModeHighlight();
9361         }
9362         /* [HGM] illegal-move claim should forfeit game when Xboard */
9363         /* only passes fully legal moves                            */
9364         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9365             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9366                                 "False illegal-move claim", GE_XBOARD );
9367             return; // do not take back move we tested as valid
9368         }
9369         currentMove = forwardMostMove-1;
9370         DisplayMove(currentMove-1); /* before DisplayMoveError */
9371         SwitchClocks(forwardMostMove-1); // [HGM] race
9372         DisplayBothClocks();
9373         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9374                 parseList[currentMove], _(cps->which));
9375         DisplayMoveError(buf1);
9376         DrawPosition(FALSE, boards[currentMove]);
9377
9378         SetUserThinkingEnables();
9379         return;
9380     }
9381     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9382         /* Program has a broken "time" command that
9383            outputs a string not ending in newline.
9384            Don't use it. */
9385         cps->sendTime = 0;
9386     }
9387     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9388         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9389             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9390     }
9391
9392     /*
9393      * If chess program startup fails, exit with an error message.
9394      * Attempts to recover here are futile. [HGM] Well, we try anyway
9395      */
9396     if ((StrStr(message, "unknown host") != NULL)
9397         || (StrStr(message, "No remote directory") != NULL)
9398         || (StrStr(message, "not found") != NULL)
9399         || (StrStr(message, "No such file") != NULL)
9400         || (StrStr(message, "can't alloc") != NULL)
9401         || (StrStr(message, "Permission denied") != NULL)) {
9402
9403         cps->maybeThinking = FALSE;
9404         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9405                 _(cps->which), cps->program, cps->host, message);
9406         RemoveInputSource(cps->isr);
9407         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9408             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9409             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9410         }
9411         return;
9412     }
9413
9414     /*
9415      * Look for hint output
9416      */
9417     if (sscanf(message, "Hint: %s", buf1) == 1) {
9418         if (cps == &first && hintRequested) {
9419             hintRequested = FALSE;
9420             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9421                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9422                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9423                                     PosFlags(forwardMostMove),
9424                                     fromY, fromX, toY, toX, promoChar, buf1);
9425                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9426                 DisplayInformation(buf2);
9427             } else {
9428                 /* Hint move could not be parsed!? */
9429               snprintf(buf2, sizeof(buf2),
9430                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9431                         buf1, _(cps->which));
9432                 DisplayError(buf2, 0);
9433             }
9434         } else {
9435           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9436         }
9437         return;
9438     }
9439
9440     /*
9441      * Ignore other messages if game is not in progress
9442      */
9443     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9444         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9445
9446     /*
9447      * look for win, lose, draw, or draw offer
9448      */
9449     if (strncmp(message, "1-0", 3) == 0) {
9450         char *p, *q, *r = "";
9451         p = strchr(message, '{');
9452         if (p) {
9453             q = strchr(p, '}');
9454             if (q) {
9455                 *q = NULLCHAR;
9456                 r = p + 1;
9457             }
9458         }
9459         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9460         return;
9461     } else if (strncmp(message, "0-1", 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         /* Kludge for Arasan 4.1 bug */
9472         if (strcmp(r, "Black resigns") == 0) {
9473             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9474             return;
9475         }
9476         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9477         return;
9478     } else if (strncmp(message, "1/2", 3) == 0) {
9479         char *p, *q, *r = "";
9480         p = strchr(message, '{');
9481         if (p) {
9482             q = strchr(p, '}');
9483             if (q) {
9484                 *q = NULLCHAR;
9485                 r = p + 1;
9486             }
9487         }
9488
9489         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9490         return;
9491
9492     } else if (strncmp(message, "White resign", 12) == 0) {
9493         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9494         return;
9495     } else if (strncmp(message, "Black resign", 12) == 0) {
9496         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9497         return;
9498     } else if (strncmp(message, "White matches", 13) == 0 ||
9499                strncmp(message, "Black matches", 13) == 0   ) {
9500         /* [HGM] ignore GNUShogi noises */
9501         return;
9502     } else if (strncmp(message, "White", 5) == 0 &&
9503                message[5] != '(' &&
9504                StrStr(message, "Black") == NULL) {
9505         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9506         return;
9507     } else if (strncmp(message, "Black", 5) == 0 &&
9508                message[5] != '(') {
9509         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9510         return;
9511     } else if (strcmp(message, "resign") == 0 ||
9512                strcmp(message, "computer resigns") == 0) {
9513         switch (gameMode) {
9514           case MachinePlaysBlack:
9515           case IcsPlayingBlack:
9516             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9517             break;
9518           case MachinePlaysWhite:
9519           case IcsPlayingWhite:
9520             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9521             break;
9522           case TwoMachinesPlay:
9523             if (cps->twoMachinesColor[0] == 'w')
9524               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9525             else
9526               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9527             break;
9528           default:
9529             /* can't happen */
9530             break;
9531         }
9532         return;
9533     } else if (strncmp(message, "opponent mates", 14) == 0) {
9534         switch (gameMode) {
9535           case MachinePlaysBlack:
9536           case IcsPlayingBlack:
9537             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9538             break;
9539           case MachinePlaysWhite:
9540           case IcsPlayingWhite:
9541             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9542             break;
9543           case TwoMachinesPlay:
9544             if (cps->twoMachinesColor[0] == 'w')
9545               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9546             else
9547               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9548             break;
9549           default:
9550             /* can't happen */
9551             break;
9552         }
9553         return;
9554     } else if (strncmp(message, "computer mates", 14) == 0) {
9555         switch (gameMode) {
9556           case MachinePlaysBlack:
9557           case IcsPlayingBlack:
9558             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9559             break;
9560           case MachinePlaysWhite:
9561           case IcsPlayingWhite:
9562             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9563             break;
9564           case TwoMachinesPlay:
9565             if (cps->twoMachinesColor[0] == 'w')
9566               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9567             else
9568               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9569             break;
9570           default:
9571             /* can't happen */
9572             break;
9573         }
9574         return;
9575     } else if (strncmp(message, "checkmate", 9) == 0) {
9576         if (WhiteOnMove(forwardMostMove)) {
9577             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9578         } else {
9579             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9580         }
9581         return;
9582     } else if (strstr(message, "Draw") != NULL ||
9583                strstr(message, "game is a draw") != NULL) {
9584         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9585         return;
9586     } else if (strstr(message, "offer") != NULL &&
9587                strstr(message, "draw") != NULL) {
9588 #if ZIPPY
9589         if (appData.zippyPlay && first.initDone) {
9590             /* Relay offer to ICS */
9591             SendToICS(ics_prefix);
9592             SendToICS("draw\n");
9593         }
9594 #endif
9595         cps->offeredDraw = 2; /* valid until this engine moves twice */
9596         if (gameMode == TwoMachinesPlay) {
9597             if (cps->other->offeredDraw) {
9598                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9599             /* [HGM] in two-machine mode we delay relaying draw offer      */
9600             /* until after we also have move, to see if it is really claim */
9601             }
9602         } else if (gameMode == MachinePlaysWhite ||
9603                    gameMode == MachinePlaysBlack) {
9604           if (userOfferedDraw) {
9605             DisplayInformation(_("Machine accepts your draw offer"));
9606             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9607           } else {
9608             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9609           }
9610         }
9611     }
9612
9613
9614     /*
9615      * Look for thinking output
9616      */
9617     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9618           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9619                                 ) {
9620         int plylev, mvleft, mvtot, curscore, time;
9621         char mvname[MOVE_LEN];
9622         u64 nodes; // [DM]
9623         char plyext;
9624         int ignore = FALSE;
9625         int prefixHint = FALSE;
9626         mvname[0] = NULLCHAR;
9627
9628         switch (gameMode) {
9629           case MachinePlaysBlack:
9630           case IcsPlayingBlack:
9631             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9632             break;
9633           case MachinePlaysWhite:
9634           case IcsPlayingWhite:
9635             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9636             break;
9637           case AnalyzeMode:
9638           case AnalyzeFile:
9639             break;
9640           case IcsObserving: /* [DM] icsEngineAnalyze */
9641             if (!appData.icsEngineAnalyze) ignore = TRUE;
9642             break;
9643           case TwoMachinesPlay:
9644             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9645                 ignore = TRUE;
9646             }
9647             break;
9648           default:
9649             ignore = TRUE;
9650             break;
9651         }
9652
9653         if (!ignore) {
9654             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9655             buf1[0] = NULLCHAR;
9656             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9657                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9658                 char score_buf[MSG_SIZ];
9659
9660                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9661                     nodes += u64Const(0x100000000);
9662
9663                 if (plyext != ' ' && plyext != '\t') {
9664                     time *= 100;
9665                 }
9666
9667                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9668                 if( cps->scoreIsAbsolute &&
9669                     ( gameMode == MachinePlaysBlack ||
9670                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9671                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9672                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9673                      !WhiteOnMove(currentMove)
9674                     ) )
9675                 {
9676                     curscore = -curscore;
9677                 }
9678
9679                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9680
9681                 if(*bestMove) { // rememer time best EPD move was first found
9682                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9683                     ChessMove mt;
9684                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9685                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9686                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9687                 }
9688
9689                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9690                         char buf[MSG_SIZ];
9691                         FILE *f;
9692                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9693                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9694                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9695                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9696                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9697                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9698                                 fclose(f);
9699                         }
9700                         else
9701                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9702                           DisplayError(_("failed writing PV"), 0);
9703                 }
9704
9705                 tempStats.depth = plylev;
9706                 tempStats.nodes = nodes;
9707                 tempStats.time = time;
9708                 tempStats.score = curscore;
9709                 tempStats.got_only_move = 0;
9710
9711                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9712                         int ticklen;
9713
9714                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9715                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9716                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9717                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9718                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9719                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9720                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9721                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9722                 }
9723
9724                 /* Buffer overflow protection */
9725                 if (pv[0] != NULLCHAR) {
9726                     if (strlen(pv) >= sizeof(tempStats.movelist)
9727                         && appData.debugMode) {
9728                         fprintf(debugFP,
9729                                 "PV is too long; using the first %u bytes.\n",
9730                                 (unsigned) sizeof(tempStats.movelist) - 1);
9731                     }
9732
9733                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9734                 } else {
9735                     sprintf(tempStats.movelist, " no PV\n");
9736                 }
9737
9738                 if (tempStats.seen_stat) {
9739                     tempStats.ok_to_send = 1;
9740                 }
9741
9742                 if (strchr(tempStats.movelist, '(') != NULL) {
9743                     tempStats.line_is_book = 1;
9744                     tempStats.nr_moves = 0;
9745                     tempStats.moves_left = 0;
9746                 } else {
9747                     tempStats.line_is_book = 0;
9748                 }
9749
9750                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9751                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9752
9753                 SendProgramStatsToFrontend( cps, &tempStats );
9754
9755                 /*
9756                     [AS] Protect the thinkOutput buffer from overflow... this
9757                     is only useful if buf1 hasn't overflowed first!
9758                 */
9759                 if(curscore >= MATE_SCORE) 
9760                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9761                 else if(curscore <= -MATE_SCORE) 
9762                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9763                 else
9764                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9765                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9766                          plylev,
9767                          (gameMode == TwoMachinesPlay ?
9768                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9769                          score_buf,
9770                          prefixHint ? lastHint : "",
9771                          prefixHint ? " " : "" );
9772
9773                 if( buf1[0] != NULLCHAR ) {
9774                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9775
9776                     if( strlen(pv) > max_len ) {
9777                         if( appData.debugMode) {
9778                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9779                         }
9780                         pv[max_len+1] = '\0';
9781                     }
9782
9783                     strcat( thinkOutput, pv);
9784                 }
9785
9786                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9787                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9788                     DisplayMove(currentMove - 1);
9789                 }
9790                 return;
9791
9792             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9793                 /* crafty (9.25+) says "(only move) <move>"
9794                  * if there is only 1 legal move
9795                  */
9796                 sscanf(p, "(only move) %s", buf1);
9797                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9798                 sprintf(programStats.movelist, "%s (only move)", buf1);
9799                 programStats.depth = 1;
9800                 programStats.nr_moves = 1;
9801                 programStats.moves_left = 1;
9802                 programStats.nodes = 1;
9803                 programStats.time = 1;
9804                 programStats.got_only_move = 1;
9805
9806                 /* Not really, but we also use this member to
9807                    mean "line isn't going to change" (Crafty
9808                    isn't searching, so stats won't change) */
9809                 programStats.line_is_book = 1;
9810
9811                 SendProgramStatsToFrontend( cps, &programStats );
9812
9813                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9814                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9815                     DisplayMove(currentMove - 1);
9816                 }
9817                 return;
9818             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9819                               &time, &nodes, &plylev, &mvleft,
9820                               &mvtot, mvname) >= 5) {
9821                 /* The stat01: line is from Crafty (9.29+) in response
9822                    to the "." command */
9823                 programStats.seen_stat = 1;
9824                 cps->maybeThinking = TRUE;
9825
9826                 if (programStats.got_only_move || !appData.periodicUpdates)
9827                   return;
9828
9829                 programStats.depth = plylev;
9830                 programStats.time = time;
9831                 programStats.nodes = nodes;
9832                 programStats.moves_left = mvleft;
9833                 programStats.nr_moves = mvtot;
9834                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9835                 programStats.ok_to_send = 1;
9836                 programStats.movelist[0] = '\0';
9837
9838                 SendProgramStatsToFrontend( cps, &programStats );
9839
9840                 return;
9841
9842             } else if (strncmp(message,"++",2) == 0) {
9843                 /* Crafty 9.29+ outputs this */
9844                 programStats.got_fail = 2;
9845                 return;
9846
9847             } else if (strncmp(message,"--",2) == 0) {
9848                 /* Crafty 9.29+ outputs this */
9849                 programStats.got_fail = 1;
9850                 return;
9851
9852             } else if (thinkOutput[0] != NULLCHAR &&
9853                        strncmp(message, "    ", 4) == 0) {
9854                 unsigned message_len;
9855
9856                 p = message;
9857                 while (*p && *p == ' ') p++;
9858
9859                 message_len = strlen( p );
9860
9861                 /* [AS] Avoid buffer overflow */
9862                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9863                     strcat(thinkOutput, " ");
9864                     strcat(thinkOutput, p);
9865                 }
9866
9867                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9868                     strcat(programStats.movelist, " ");
9869                     strcat(programStats.movelist, p);
9870                 }
9871
9872                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9873                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9874                     DisplayMove(currentMove - 1);
9875                 }
9876                 return;
9877             }
9878         }
9879         else {
9880             buf1[0] = NULLCHAR;
9881
9882             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9883                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9884             {
9885                 ChessProgramStats cpstats;
9886
9887                 if (plyext != ' ' && plyext != '\t') {
9888                     time *= 100;
9889                 }
9890
9891                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9892                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9893                     curscore = -curscore;
9894                 }
9895
9896                 cpstats.depth = plylev;
9897                 cpstats.nodes = nodes;
9898                 cpstats.time = time;
9899                 cpstats.score = curscore;
9900                 cpstats.got_only_move = 0;
9901                 cpstats.movelist[0] = '\0';
9902
9903                 if (buf1[0] != NULLCHAR) {
9904                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9905                 }
9906
9907                 cpstats.ok_to_send = 0;
9908                 cpstats.line_is_book = 0;
9909                 cpstats.nr_moves = 0;
9910                 cpstats.moves_left = 0;
9911
9912                 SendProgramStatsToFrontend( cps, &cpstats );
9913             }
9914         }
9915     }
9916 }
9917
9918
9919 /* Parse a game score from the character string "game", and
9920    record it as the history of the current game.  The game
9921    score is NOT assumed to start from the standard position.
9922    The display is not updated in any way.
9923    */
9924 void
9925 ParseGameHistory (char *game)
9926 {
9927     ChessMove moveType;
9928     int fromX, fromY, toX, toY, boardIndex;
9929     char promoChar;
9930     char *p, *q;
9931     char buf[MSG_SIZ];
9932
9933     if (appData.debugMode)
9934       fprintf(debugFP, "Parsing game history: %s\n", game);
9935
9936     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9937     gameInfo.site = StrSave(appData.icsHost);
9938     gameInfo.date = PGNDate();
9939     gameInfo.round = StrSave("-");
9940
9941     /* Parse out names of players */
9942     while (*game == ' ') game++;
9943     p = buf;
9944     while (*game != ' ') *p++ = *game++;
9945     *p = NULLCHAR;
9946     gameInfo.white = StrSave(buf);
9947     while (*game == ' ') game++;
9948     p = buf;
9949     while (*game != ' ' && *game != '\n') *p++ = *game++;
9950     *p = NULLCHAR;
9951     gameInfo.black = StrSave(buf);
9952
9953     /* Parse moves */
9954     boardIndex = blackPlaysFirst ? 1 : 0;
9955     yynewstr(game);
9956     for (;;) {
9957         yyboardindex = boardIndex;
9958         moveType = (ChessMove) Myylex();
9959         switch (moveType) {
9960           case IllegalMove:             /* maybe suicide chess, etc. */
9961   if (appData.debugMode) {
9962     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9963     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9964     setbuf(debugFP, NULL);
9965   }
9966           case WhitePromotion:
9967           case BlackPromotion:
9968           case WhiteNonPromotion:
9969           case BlackNonPromotion:
9970           case NormalMove:
9971           case FirstLeg:
9972           case WhiteCapturesEnPassant:
9973           case BlackCapturesEnPassant:
9974           case WhiteKingSideCastle:
9975           case WhiteQueenSideCastle:
9976           case BlackKingSideCastle:
9977           case BlackQueenSideCastle:
9978           case WhiteKingSideCastleWild:
9979           case WhiteQueenSideCastleWild:
9980           case BlackKingSideCastleWild:
9981           case BlackQueenSideCastleWild:
9982           /* PUSH Fabien */
9983           case WhiteHSideCastleFR:
9984           case WhiteASideCastleFR:
9985           case BlackHSideCastleFR:
9986           case BlackASideCastleFR:
9987           /* POP Fabien */
9988             fromX = currentMoveString[0] - AAA;
9989             fromY = currentMoveString[1] - ONE;
9990             toX = currentMoveString[2] - AAA;
9991             toY = currentMoveString[3] - ONE;
9992             promoChar = currentMoveString[4];
9993             break;
9994           case WhiteDrop:
9995           case BlackDrop:
9996             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9997             fromX = moveType == WhiteDrop ?
9998               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9999             (int) CharToPiece(ToLower(currentMoveString[0]));
10000             fromY = DROP_RANK;
10001             toX = currentMoveString[2] - AAA;
10002             toY = currentMoveString[3] - ONE;
10003             promoChar = NULLCHAR;
10004             break;
10005           case AmbiguousMove:
10006             /* bug? */
10007             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10008   if (appData.debugMode) {
10009     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10010     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10011     setbuf(debugFP, NULL);
10012   }
10013             DisplayError(buf, 0);
10014             return;
10015           case ImpossibleMove:
10016             /* bug? */
10017             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10018   if (appData.debugMode) {
10019     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10020     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10021     setbuf(debugFP, NULL);
10022   }
10023             DisplayError(buf, 0);
10024             return;
10025           case EndOfFile:
10026             if (boardIndex < backwardMostMove) {
10027                 /* Oops, gap.  How did that happen? */
10028                 DisplayError(_("Gap in move list"), 0);
10029                 return;
10030             }
10031             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10032             if (boardIndex > forwardMostMove) {
10033                 forwardMostMove = boardIndex;
10034             }
10035             return;
10036           case ElapsedTime:
10037             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10038                 strcat(parseList[boardIndex-1], " ");
10039                 strcat(parseList[boardIndex-1], yy_text);
10040             }
10041             continue;
10042           case Comment:
10043           case PGNTag:
10044           case NAG:
10045           default:
10046             /* ignore */
10047             continue;
10048           case WhiteWins:
10049           case BlackWins:
10050           case GameIsDrawn:
10051           case GameUnfinished:
10052             if (gameMode == IcsExamining) {
10053                 if (boardIndex < backwardMostMove) {
10054                     /* Oops, gap.  How did that happen? */
10055                     return;
10056                 }
10057                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10058                 return;
10059             }
10060             gameInfo.result = moveType;
10061             p = strchr(yy_text, '{');
10062             if (p == NULL) p = strchr(yy_text, '(');
10063             if (p == NULL) {
10064                 p = yy_text;
10065                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10066             } else {
10067                 q = strchr(p, *p == '{' ? '}' : ')');
10068                 if (q != NULL) *q = NULLCHAR;
10069                 p++;
10070             }
10071             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10072             gameInfo.resultDetails = StrSave(p);
10073             continue;
10074         }
10075         if (boardIndex >= forwardMostMove &&
10076             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10077             backwardMostMove = blackPlaysFirst ? 1 : 0;
10078             return;
10079         }
10080         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10081                                  fromY, fromX, toY, toX, promoChar,
10082                                  parseList[boardIndex]);
10083         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10084         /* currentMoveString is set as a side-effect of yylex */
10085         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10086         strcat(moveList[boardIndex], "\n");
10087         boardIndex++;
10088         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10089         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10090           case MT_NONE:
10091           case MT_STALEMATE:
10092           default:
10093             break;
10094           case MT_CHECK:
10095             if(!IS_SHOGI(gameInfo.variant))
10096                 strcat(parseList[boardIndex - 1], "+");
10097             break;
10098           case MT_CHECKMATE:
10099           case MT_STAINMATE:
10100             strcat(parseList[boardIndex - 1], "#");
10101             break;
10102         }
10103     }
10104 }
10105
10106
10107 /* Apply a move to the given board  */
10108 void
10109 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10110 {
10111   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10112   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10113
10114     /* [HGM] compute & store e.p. status and castling rights for new position */
10115     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10116
10117       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10118       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10119       board[EP_STATUS] = EP_NONE;
10120       board[EP_FILE] = board[EP_RANK] = 100;
10121
10122   if (fromY == DROP_RANK) {
10123         /* must be first */
10124         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10125             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10126             return;
10127         }
10128         piece = board[toY][toX] = (ChessSquare) fromX;
10129   } else {
10130 //      ChessSquare victim;
10131       int i;
10132
10133       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10134 //           victim = board[killY][killX],
10135            killed = board[killY][killX],
10136            board[killY][killX] = EmptySquare,
10137            board[EP_STATUS] = EP_CAPTURE;
10138            if( kill2X >= 0 && kill2Y >= 0)
10139              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10140       }
10141
10142       if( board[toY][toX] != EmptySquare ) {
10143            board[EP_STATUS] = EP_CAPTURE;
10144            if( (fromX != toX || fromY != toY) && // not igui!
10145                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10146                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10147                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10148            }
10149       }
10150
10151       pawn = board[fromY][fromX];
10152       if( pawn == WhiteLance || pawn == BlackLance ) {
10153            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10154                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10155                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10156            }
10157       }
10158       if( pawn == WhitePawn ) {
10159            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10160                board[EP_STATUS] = EP_PAWN_MOVE;
10161            if( toY-fromY>=2) {
10162                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10163                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10164                         gameInfo.variant != VariantBerolina || toX < fromX)
10165                       board[EP_STATUS] = toX | berolina;
10166                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10167                         gameInfo.variant != VariantBerolina || toX > fromX)
10168                       board[EP_STATUS] = toX;
10169            }
10170       } else
10171       if( pawn == BlackPawn ) {
10172            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10173                board[EP_STATUS] = EP_PAWN_MOVE;
10174            if( toY-fromY<= -2) {
10175                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10176                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10177                         gameInfo.variant != VariantBerolina || toX < fromX)
10178                       board[EP_STATUS] = toX | berolina;
10179                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10180                         gameInfo.variant != VariantBerolina || toX > fromX)
10181                       board[EP_STATUS] = toX;
10182            }
10183        }
10184
10185        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10186        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10187        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10188        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10189
10190        for(i=0; i<nrCastlingRights; i++) {
10191            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10192               board[CASTLING][i] == toX   && castlingRank[i] == toY
10193              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10194        }
10195
10196        if(gameInfo.variant == VariantSChess) { // update virginity
10197            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10198            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10199            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10200            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10201        }
10202
10203      if (fromX == toX && fromY == toY) return;
10204
10205      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10206      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10207      if(gameInfo.variant == VariantKnightmate)
10208          king += (int) WhiteUnicorn - (int) WhiteKing;
10209
10210     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10211        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10212         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10213         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10214         board[EP_STATUS] = EP_NONE; // capture was fake!
10215     } else
10216     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10217         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10218         board[toY][toX] = piece;
10219         board[EP_STATUS] = EP_NONE; // capture was fake!
10220     } else
10221     /* Code added by Tord: */
10222     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10223     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10224         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10225       board[EP_STATUS] = EP_NONE; // capture was fake!
10226       board[fromY][fromX] = EmptySquare;
10227       board[toY][toX] = EmptySquare;
10228       if((toX > fromX) != (piece == WhiteRook)) {
10229         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10230       } else {
10231         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10232       }
10233     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10234                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10235       board[EP_STATUS] = EP_NONE;
10236       board[fromY][fromX] = EmptySquare;
10237       board[toY][toX] = EmptySquare;
10238       if((toX > fromX) != (piece == BlackRook)) {
10239         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10240       } else {
10241         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10242       }
10243     /* End of code added by Tord */
10244
10245     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10246         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10247         board[toY][toX] = piece;
10248     } else if (board[fromY][fromX] == king
10249         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10250         && toY == fromY && toX > fromX+1) {
10251         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10252         board[fromY][toX-1] = board[fromY][rookX];
10253         board[fromY][rookX] = EmptySquare;
10254         board[fromY][fromX] = EmptySquare;
10255         board[toY][toX] = king;
10256     } else if (board[fromY][fromX] == king
10257         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10258                && toY == fromY && toX < fromX-1) {
10259         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10260         board[fromY][toX+1] = board[fromY][rookX];
10261         board[fromY][rookX] = EmptySquare;
10262         board[fromY][fromX] = EmptySquare;
10263         board[toY][toX] = king;
10264     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10265                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10266                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10267                ) {
10268         /* white pawn promotion */
10269         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10270         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10271             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10272         board[fromY][fromX] = EmptySquare;
10273     } else if ((fromY >= BOARD_HEIGHT>>1)
10274                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10275                && (toX != fromX)
10276                && gameInfo.variant != VariantXiangqi
10277                && gameInfo.variant != VariantBerolina
10278                && (pawn == WhitePawn)
10279                && (board[toY][toX] == EmptySquare)) {
10280         board[fromY][fromX] = EmptySquare;
10281         board[toY][toX] = piece;
10282         if(toY == epRank - 128 + 1)
10283             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10284         else
10285             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10286     } else if ((fromY == BOARD_HEIGHT-4)
10287                && (toX == fromX)
10288                && gameInfo.variant == VariantBerolina
10289                && (board[fromY][fromX] == WhitePawn)
10290                && (board[toY][toX] == EmptySquare)) {
10291         board[fromY][fromX] = EmptySquare;
10292         board[toY][toX] = WhitePawn;
10293         if(oldEP & EP_BEROLIN_A) {
10294                 captured = board[fromY][fromX-1];
10295                 board[fromY][fromX-1] = EmptySquare;
10296         }else{  captured = board[fromY][fromX+1];
10297                 board[fromY][fromX+1] = EmptySquare;
10298         }
10299     } else if (board[fromY][fromX] == king
10300         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10301                && toY == fromY && toX > fromX+1) {
10302         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10303         board[fromY][toX-1] = board[fromY][rookX];
10304         board[fromY][rookX] = EmptySquare;
10305         board[fromY][fromX] = EmptySquare;
10306         board[toY][toX] = king;
10307     } else if (board[fromY][fromX] == king
10308         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10309                && toY == fromY && toX < fromX-1) {
10310         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10311         board[fromY][toX+1] = board[fromY][rookX];
10312         board[fromY][rookX] = EmptySquare;
10313         board[fromY][fromX] = EmptySquare;
10314         board[toY][toX] = king;
10315     } else if (fromY == 7 && fromX == 3
10316                && board[fromY][fromX] == BlackKing
10317                && toY == 7 && toX == 5) {
10318         board[fromY][fromX] = EmptySquare;
10319         board[toY][toX] = BlackKing;
10320         board[fromY][7] = EmptySquare;
10321         board[toY][4] = BlackRook;
10322     } else if (fromY == 7 && fromX == 3
10323                && board[fromY][fromX] == BlackKing
10324                && toY == 7 && toX == 1) {
10325         board[fromY][fromX] = EmptySquare;
10326         board[toY][toX] = BlackKing;
10327         board[fromY][0] = EmptySquare;
10328         board[toY][2] = BlackRook;
10329     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10330                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10331                && toY < promoRank && promoChar
10332                ) {
10333         /* black pawn promotion */
10334         board[toY][toX] = CharToPiece(ToLower(promoChar));
10335         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10336             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10337         board[fromY][fromX] = EmptySquare;
10338     } else if ((fromY < BOARD_HEIGHT>>1)
10339                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10340                && (toX != fromX)
10341                && gameInfo.variant != VariantXiangqi
10342                && gameInfo.variant != VariantBerolina
10343                && (pawn == BlackPawn)
10344                && (board[toY][toX] == EmptySquare)) {
10345         board[fromY][fromX] = EmptySquare;
10346         board[toY][toX] = piece;
10347         if(toY == epRank - 128 - 1)
10348             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10349         else
10350             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10351     } else if ((fromY == 3)
10352                && (toX == fromX)
10353                && gameInfo.variant == VariantBerolina
10354                && (board[fromY][fromX] == BlackPawn)
10355                && (board[toY][toX] == EmptySquare)) {
10356         board[fromY][fromX] = EmptySquare;
10357         board[toY][toX] = BlackPawn;
10358         if(oldEP & EP_BEROLIN_A) {
10359                 captured = board[fromY][fromX-1];
10360                 board[fromY][fromX-1] = EmptySquare;
10361         }else{  captured = board[fromY][fromX+1];
10362                 board[fromY][fromX+1] = EmptySquare;
10363         }
10364     } else {
10365         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10366         board[fromY][fromX] = EmptySquare;
10367         board[toY][toX] = piece;
10368     }
10369   }
10370
10371     if (gameInfo.holdingsWidth != 0) {
10372
10373       /* !!A lot more code needs to be written to support holdings  */
10374       /* [HGM] OK, so I have written it. Holdings are stored in the */
10375       /* penultimate board files, so they are automaticlly stored   */
10376       /* in the game history.                                       */
10377       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10378                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10379         /* Delete from holdings, by decreasing count */
10380         /* and erasing image if necessary            */
10381         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10382         if(p < (int) BlackPawn) { /* white drop */
10383              p -= (int)WhitePawn;
10384                  p = PieceToNumber((ChessSquare)p);
10385              if(p >= gameInfo.holdingsSize) p = 0;
10386              if(--board[p][BOARD_WIDTH-2] <= 0)
10387                   board[p][BOARD_WIDTH-1] = EmptySquare;
10388              if((int)board[p][BOARD_WIDTH-2] < 0)
10389                         board[p][BOARD_WIDTH-2] = 0;
10390         } else {                  /* black drop */
10391              p -= (int)BlackPawn;
10392                  p = PieceToNumber((ChessSquare)p);
10393              if(p >= gameInfo.holdingsSize) p = 0;
10394              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10395                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10396              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10397                         board[BOARD_HEIGHT-1-p][1] = 0;
10398         }
10399       }
10400       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10401           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10402         /* [HGM] holdings: Add to holdings, if holdings exist */
10403         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10404                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10405                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10406         }
10407         p = (int) captured;
10408         if (p >= (int) BlackPawn) {
10409           p -= (int)BlackPawn;
10410           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10411                   /* Restore shogi-promoted piece to its original  first */
10412                   captured = (ChessSquare) (DEMOTED(captured));
10413                   p = DEMOTED(p);
10414           }
10415           p = PieceToNumber((ChessSquare)p);
10416           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10417           board[p][BOARD_WIDTH-2]++;
10418           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10419         } else {
10420           p -= (int)WhitePawn;
10421           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10422                   captured = (ChessSquare) (DEMOTED(captured));
10423                   p = DEMOTED(p);
10424           }
10425           p = PieceToNumber((ChessSquare)p);
10426           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10427           board[BOARD_HEIGHT-1-p][1]++;
10428           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10429         }
10430       }
10431     } else if (gameInfo.variant == VariantAtomic) {
10432       if (captured != EmptySquare) {
10433         int y, x;
10434         for (y = toY-1; y <= toY+1; y++) {
10435           for (x = toX-1; x <= toX+1; x++) {
10436             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10437                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10438               board[y][x] = EmptySquare;
10439             }
10440           }
10441         }
10442         board[toY][toX] = EmptySquare;
10443       }
10444     }
10445
10446     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10447         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10448     } else
10449     if(promoChar == '+') {
10450         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10451         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10452         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10453           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10454     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10455         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10456         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10457            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10458         board[toY][toX] = newPiece;
10459     }
10460     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10461                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10462         // [HGM] superchess: take promotion piece out of holdings
10463         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10464         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10465             if(!--board[k][BOARD_WIDTH-2])
10466                 board[k][BOARD_WIDTH-1] = EmptySquare;
10467         } else {
10468             if(!--board[BOARD_HEIGHT-1-k][1])
10469                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10470         }
10471     }
10472 }
10473
10474 /* Updates forwardMostMove */
10475 void
10476 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10477 {
10478     int x = toX, y = toY;
10479     char *s = parseList[forwardMostMove];
10480     ChessSquare p = boards[forwardMostMove][toY][toX];
10481 //    forwardMostMove++; // [HGM] bare: moved downstream
10482
10483     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10484     (void) CoordsToAlgebraic(boards[forwardMostMove],
10485                              PosFlags(forwardMostMove),
10486                              fromY, fromX, y, x, (killX < 0)*promoChar,
10487                              s);
10488     if(killX >= 0 && killY >= 0)
10489         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0', promoChar);
10490
10491     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10492         int timeLeft; static int lastLoadFlag=0; int king, piece;
10493         piece = boards[forwardMostMove][fromY][fromX];
10494         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10495         if(gameInfo.variant == VariantKnightmate)
10496             king += (int) WhiteUnicorn - (int) WhiteKing;
10497         if(forwardMostMove == 0) {
10498             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10499                 fprintf(serverMoves, "%s;", UserName());
10500             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10501                 fprintf(serverMoves, "%s;", second.tidy);
10502             fprintf(serverMoves, "%s;", first.tidy);
10503             if(gameMode == MachinePlaysWhite)
10504                 fprintf(serverMoves, "%s;", UserName());
10505             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10506                 fprintf(serverMoves, "%s;", second.tidy);
10507         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10508         lastLoadFlag = loadFlag;
10509         // print base move
10510         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10511         // print castling suffix
10512         if( toY == fromY && piece == king ) {
10513             if(toX-fromX > 1)
10514                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10515             if(fromX-toX >1)
10516                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10517         }
10518         // e.p. suffix
10519         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10520              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10521              boards[forwardMostMove][toY][toX] == EmptySquare
10522              && fromX != toX && fromY != toY)
10523                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10524         // promotion suffix
10525         if(promoChar != NULLCHAR) {
10526             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10527                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10528                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10529             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10530         }
10531         if(!loadFlag) {
10532                 char buf[MOVE_LEN*2], *p; int len;
10533             fprintf(serverMoves, "/%d/%d",
10534                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10535             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10536             else                      timeLeft = blackTimeRemaining/1000;
10537             fprintf(serverMoves, "/%d", timeLeft);
10538                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10539                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10540                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10541                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10542             fprintf(serverMoves, "/%s", buf);
10543         }
10544         fflush(serverMoves);
10545     }
10546
10547     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10548         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10549       return;
10550     }
10551     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10552     if (commentList[forwardMostMove+1] != NULL) {
10553         free(commentList[forwardMostMove+1]);
10554         commentList[forwardMostMove+1] = NULL;
10555     }
10556     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10557     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10558     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10559     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10560     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10561     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10562     adjustedClock = FALSE;
10563     gameInfo.result = GameUnfinished;
10564     if (gameInfo.resultDetails != NULL) {
10565         free(gameInfo.resultDetails);
10566         gameInfo.resultDetails = NULL;
10567     }
10568     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10569                               moveList[forwardMostMove - 1]);
10570     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10571       case MT_NONE:
10572       case MT_STALEMATE:
10573       default:
10574         break;
10575       case MT_CHECK:
10576         if(!IS_SHOGI(gameInfo.variant))
10577             strcat(parseList[forwardMostMove - 1], "+");
10578         break;
10579       case MT_CHECKMATE:
10580       case MT_STAINMATE:
10581         strcat(parseList[forwardMostMove - 1], "#");
10582         break;
10583     }
10584 }
10585
10586 /* Updates currentMove if not pausing */
10587 void
10588 ShowMove (int fromX, int fromY, int toX, int toY)
10589 {
10590     int instant = (gameMode == PlayFromGameFile) ?
10591         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10592     if(appData.noGUI) return;
10593     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10594         if (!instant) {
10595             if (forwardMostMove == currentMove + 1) {
10596                 AnimateMove(boards[forwardMostMove - 1],
10597                             fromX, fromY, toX, toY);
10598             }
10599         }
10600         currentMove = forwardMostMove;
10601     }
10602
10603     killX = killY = -1; // [HGM] lion: used up
10604
10605     if (instant) return;
10606
10607     DisplayMove(currentMove - 1);
10608     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10609             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10610                 SetHighlights(fromX, fromY, toX, toY);
10611             }
10612     }
10613     DrawPosition(FALSE, boards[currentMove]);
10614     DisplayBothClocks();
10615     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10616 }
10617
10618 void
10619 SendEgtPath (ChessProgramState *cps)
10620 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10621         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10622
10623         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10624
10625         while(*p) {
10626             char c, *q = name+1, *r, *s;
10627
10628             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10629             while(*p && *p != ',') *q++ = *p++;
10630             *q++ = ':'; *q = 0;
10631             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10632                 strcmp(name, ",nalimov:") == 0 ) {
10633                 // take nalimov path from the menu-changeable option first, if it is defined
10634               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10635                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10636             } else
10637             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10638                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10639                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10640                 s = r = StrStr(s, ":") + 1; // beginning of path info
10641                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10642                 c = *r; *r = 0;             // temporarily null-terminate path info
10643                     *--q = 0;               // strip of trailig ':' from name
10644                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10645                 *r = c;
10646                 SendToProgram(buf,cps);     // send egtbpath command for this format
10647             }
10648             if(*p == ',') p++; // read away comma to position for next format name
10649         }
10650 }
10651
10652 static int
10653 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10654 {
10655       int width = 8, height = 8, holdings = 0;             // most common sizes
10656       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10657       // correct the deviations default for each variant
10658       if( v == VariantXiangqi ) width = 9,  height = 10;
10659       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10660       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10661       if( v == VariantCapablanca || v == VariantCapaRandom ||
10662           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10663                                 width = 10;
10664       if( v == VariantCourier ) width = 12;
10665       if( v == VariantSuper )                            holdings = 8;
10666       if( v == VariantGreat )   width = 10,              holdings = 8;
10667       if( v == VariantSChess )                           holdings = 7;
10668       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10669       if( v == VariantChuChess) width = 10, height = 10;
10670       if( v == VariantChu )     width = 12, height = 12;
10671       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10672              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10673              holdingsSize >= 0 && holdingsSize != holdings;
10674 }
10675
10676 char variantError[MSG_SIZ];
10677
10678 char *
10679 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10680 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10681       char *p, *variant = VariantName(v);
10682       static char b[MSG_SIZ];
10683       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10684            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10685                                                holdingsSize, variant); // cook up sized variant name
10686            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10687            if(StrStr(list, b) == NULL) {
10688                // specific sized variant not known, check if general sizing allowed
10689                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10690                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10691                             boardWidth, boardHeight, holdingsSize, engine);
10692                    return NULL;
10693                }
10694                /* [HGM] here we really should compare with the maximum supported board size */
10695            }
10696       } else snprintf(b, MSG_SIZ,"%s", variant);
10697       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10698       p = StrStr(list, b);
10699       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10700       if(p == NULL) {
10701           // occurs not at all in list, or only as sub-string
10702           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10703           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10704               int l = strlen(variantError);
10705               char *q;
10706               while(p != list && p[-1] != ',') p--;
10707               q = strchr(p, ',');
10708               if(q) *q = NULLCHAR;
10709               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10710               if(q) *q= ',';
10711           }
10712           return NULL;
10713       }
10714       return b;
10715 }
10716
10717 void
10718 InitChessProgram (ChessProgramState *cps, int setup)
10719 /* setup needed to setup FRC opening position */
10720 {
10721     char buf[MSG_SIZ], *b;
10722     if (appData.noChessProgram) return;
10723     hintRequested = FALSE;
10724     bookRequested = FALSE;
10725
10726     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10727     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10728     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10729     if(cps->memSize) { /* [HGM] memory */
10730       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10731         SendToProgram(buf, cps);
10732     }
10733     SendEgtPath(cps); /* [HGM] EGT */
10734     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10735       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10736         SendToProgram(buf, cps);
10737     }
10738
10739     setboardSpoiledMachineBlack = FALSE;
10740     SendToProgram(cps->initString, cps);
10741     if (gameInfo.variant != VariantNormal &&
10742         gameInfo.variant != VariantLoadable
10743         /* [HGM] also send variant if board size non-standard */
10744         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10745
10746       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10747                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10748       if (b == NULL) {
10749         VariantClass v;
10750         char c, *q = cps->variants, *p = strchr(q, ',');
10751         if(p) *p = NULLCHAR;
10752         v = StringToVariant(q);
10753         DisplayError(variantError, 0);
10754         if(v != VariantUnknown && cps == &first) {
10755             int w, h, s;
10756             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10757                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10758             ASSIGN(appData.variant, q);
10759             Reset(TRUE, FALSE);
10760         }
10761         if(p) *p = ',';
10762         return;
10763       }
10764
10765       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10766       SendToProgram(buf, cps);
10767     }
10768     currentlyInitializedVariant = gameInfo.variant;
10769
10770     /* [HGM] send opening position in FRC to first engine */
10771     if(setup) {
10772           SendToProgram("force\n", cps);
10773           SendBoard(cps, 0);
10774           /* engine is now in force mode! Set flag to wake it up after first move. */
10775           setboardSpoiledMachineBlack = 1;
10776     }
10777
10778     if (cps->sendICS) {
10779       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10780       SendToProgram(buf, cps);
10781     }
10782     cps->maybeThinking = FALSE;
10783     cps->offeredDraw = 0;
10784     if (!appData.icsActive) {
10785         SendTimeControl(cps, movesPerSession, timeControl,
10786                         timeIncrement, appData.searchDepth,
10787                         searchTime);
10788     }
10789     if (appData.showThinking
10790         // [HGM] thinking: four options require thinking output to be sent
10791         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10792                                 ) {
10793         SendToProgram("post\n", cps);
10794     }
10795     SendToProgram("hard\n", cps);
10796     if (!appData.ponderNextMove) {
10797         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10798            it without being sure what state we are in first.  "hard"
10799            is not a toggle, so that one is OK.
10800          */
10801         SendToProgram("easy\n", cps);
10802     }
10803     if (cps->usePing) {
10804       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10805       SendToProgram(buf, cps);
10806     }
10807     cps->initDone = TRUE;
10808     ClearEngineOutputPane(cps == &second);
10809 }
10810
10811
10812 void
10813 ResendOptions (ChessProgramState *cps)
10814 { // send the stored value of the options
10815   int i;
10816   char buf[MSG_SIZ];
10817   Option *opt = cps->option;
10818   for(i=0; i<cps->nrOptions; i++, opt++) {
10819       switch(opt->type) {
10820         case Spin:
10821         case Slider:
10822         case CheckBox:
10823             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10824           break;
10825         case ComboBox:
10826           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10827           break;
10828         default:
10829             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10830           break;
10831         case Button:
10832         case SaveButton:
10833           continue;
10834       }
10835       SendToProgram(buf, cps);
10836   }
10837 }
10838
10839 void
10840 StartChessProgram (ChessProgramState *cps)
10841 {
10842     char buf[MSG_SIZ];
10843     int err;
10844
10845     if (appData.noChessProgram) return;
10846     cps->initDone = FALSE;
10847
10848     if (strcmp(cps->host, "localhost") == 0) {
10849         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10850     } else if (*appData.remoteShell == NULLCHAR) {
10851         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10852     } else {
10853         if (*appData.remoteUser == NULLCHAR) {
10854           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10855                     cps->program);
10856         } else {
10857           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10858                     cps->host, appData.remoteUser, cps->program);
10859         }
10860         err = StartChildProcess(buf, "", &cps->pr);
10861     }
10862
10863     if (err != 0) {
10864       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10865         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10866         if(cps != &first) return;
10867         appData.noChessProgram = TRUE;
10868         ThawUI();
10869         SetNCPMode();
10870 //      DisplayFatalError(buf, err, 1);
10871 //      cps->pr = NoProc;
10872 //      cps->isr = NULL;
10873         return;
10874     }
10875
10876     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10877     if (cps->protocolVersion > 1) {
10878       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10879       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10880         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10881         cps->comboCnt = 0;  //                and values of combo boxes
10882       }
10883       SendToProgram(buf, cps);
10884       if(cps->reload) ResendOptions(cps);
10885     } else {
10886       SendToProgram("xboard\n", cps);
10887     }
10888 }
10889
10890 void
10891 TwoMachinesEventIfReady P((void))
10892 {
10893   static int curMess = 0;
10894   if (first.lastPing != first.lastPong) {
10895     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10896     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10897     return;
10898   }
10899   if (second.lastPing != second.lastPong) {
10900     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10901     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10902     return;
10903   }
10904   DisplayMessage("", ""); curMess = 0;
10905   TwoMachinesEvent();
10906 }
10907
10908 char *
10909 MakeName (char *template)
10910 {
10911     time_t clock;
10912     struct tm *tm;
10913     static char buf[MSG_SIZ];
10914     char *p = buf;
10915     int i;
10916
10917     clock = time((time_t *)NULL);
10918     tm = localtime(&clock);
10919
10920     while(*p++ = *template++) if(p[-1] == '%') {
10921         switch(*template++) {
10922           case 0:   *p = 0; return buf;
10923           case 'Y': i = tm->tm_year+1900; break;
10924           case 'y': i = tm->tm_year-100; break;
10925           case 'M': i = tm->tm_mon+1; break;
10926           case 'd': i = tm->tm_mday; break;
10927           case 'h': i = tm->tm_hour; break;
10928           case 'm': i = tm->tm_min; break;
10929           case 's': i = tm->tm_sec; break;
10930           default:  i = 0;
10931         }
10932         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10933     }
10934     return buf;
10935 }
10936
10937 int
10938 CountPlayers (char *p)
10939 {
10940     int n = 0;
10941     while(p = strchr(p, '\n')) p++, n++; // count participants
10942     return n;
10943 }
10944
10945 FILE *
10946 WriteTourneyFile (char *results, FILE *f)
10947 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10948     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10949     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10950         // create a file with tournament description
10951         fprintf(f, "-participants {%s}\n", appData.participants);
10952         fprintf(f, "-seedBase %d\n", appData.seedBase);
10953         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10954         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10955         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10956         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10957         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10958         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10959         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10960         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10961         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10962         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10963         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10964         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10965         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10966         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10967         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10968         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10969         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10970         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10971         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10972         fprintf(f, "-smpCores %d\n", appData.smpCores);
10973         if(searchTime > 0)
10974                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10975         else {
10976                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10977                 fprintf(f, "-tc %s\n", appData.timeControl);
10978                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10979         }
10980         fprintf(f, "-results \"%s\"\n", results);
10981     }
10982     return f;
10983 }
10984
10985 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10986
10987 void
10988 Substitute (char *participants, int expunge)
10989 {
10990     int i, changed, changes=0, nPlayers=0;
10991     char *p, *q, *r, buf[MSG_SIZ];
10992     if(participants == NULL) return;
10993     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10994     r = p = participants; q = appData.participants;
10995     while(*p && *p == *q) {
10996         if(*p == '\n') r = p+1, nPlayers++;
10997         p++; q++;
10998     }
10999     if(*p) { // difference
11000         while(*p && *p++ != '\n');
11001         while(*q && *q++ != '\n');
11002       changed = nPlayers;
11003         changes = 1 + (strcmp(p, q) != 0);
11004     }
11005     if(changes == 1) { // a single engine mnemonic was changed
11006         q = r; while(*q) nPlayers += (*q++ == '\n');
11007         p = buf; while(*r && (*p = *r++) != '\n') p++;
11008         *p = NULLCHAR;
11009         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11010         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11011         if(mnemonic[i]) { // The substitute is valid
11012             FILE *f;
11013             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11014                 flock(fileno(f), LOCK_EX);
11015                 ParseArgsFromFile(f);
11016                 fseek(f, 0, SEEK_SET);
11017                 FREE(appData.participants); appData.participants = participants;
11018                 if(expunge) { // erase results of replaced engine
11019                     int len = strlen(appData.results), w, b, dummy;
11020                     for(i=0; i<len; i++) {
11021                         Pairing(i, nPlayers, &w, &b, &dummy);
11022                         if((w == changed || b == changed) && appData.results[i] == '*') {
11023                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11024                             fclose(f);
11025                             return;
11026                         }
11027                     }
11028                     for(i=0; i<len; i++) {
11029                         Pairing(i, nPlayers, &w, &b, &dummy);
11030                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11031                     }
11032                 }
11033                 WriteTourneyFile(appData.results, f);
11034                 fclose(f); // release lock
11035                 return;
11036             }
11037         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11038     }
11039     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11040     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11041     free(participants);
11042     return;
11043 }
11044
11045 int
11046 CheckPlayers (char *participants)
11047 {
11048         int i;
11049         char buf[MSG_SIZ], *p;
11050         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11051         while(p = strchr(participants, '\n')) {
11052             *p = NULLCHAR;
11053             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11054             if(!mnemonic[i]) {
11055                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11056                 *p = '\n';
11057                 DisplayError(buf, 0);
11058                 return 1;
11059             }
11060             *p = '\n';
11061             participants = p + 1;
11062         }
11063         return 0;
11064 }
11065
11066 int
11067 CreateTourney (char *name)
11068 {
11069         FILE *f;
11070         if(matchMode && strcmp(name, appData.tourneyFile)) {
11071              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11072         }
11073         if(name[0] == NULLCHAR) {
11074             if(appData.participants[0])
11075                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11076             return 0;
11077         }
11078         f = fopen(name, "r");
11079         if(f) { // file exists
11080             ASSIGN(appData.tourneyFile, name);
11081             ParseArgsFromFile(f); // parse it
11082         } else {
11083             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11084             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11085                 DisplayError(_("Not enough participants"), 0);
11086                 return 0;
11087             }
11088             if(CheckPlayers(appData.participants)) return 0;
11089             ASSIGN(appData.tourneyFile, name);
11090             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11091             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11092         }
11093         fclose(f);
11094         appData.noChessProgram = FALSE;
11095         appData.clockMode = TRUE;
11096         SetGNUMode();
11097         return 1;
11098 }
11099
11100 int
11101 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11102 {
11103     char buf[MSG_SIZ], *p, *q;
11104     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11105     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11106     skip = !all && group[0]; // if group requested, we start in skip mode
11107     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11108         p = names; q = buf; header = 0;
11109         while(*p && *p != '\n') *q++ = *p++;
11110         *q = 0;
11111         if(*p == '\n') p++;
11112         if(buf[0] == '#') {
11113             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11114             depth++; // we must be entering a new group
11115             if(all) continue; // suppress printing group headers when complete list requested
11116             header = 1;
11117             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11118         }
11119         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11120         if(engineList[i]) free(engineList[i]);
11121         engineList[i] = strdup(buf);
11122         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11123         if(engineMnemonic[i]) free(engineMnemonic[i]);
11124         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11125             strcat(buf, " (");
11126             sscanf(q + 8, "%s", buf + strlen(buf));
11127             strcat(buf, ")");
11128         }
11129         engineMnemonic[i] = strdup(buf);
11130         i++;
11131     }
11132     engineList[i] = engineMnemonic[i] = NULL;
11133     return i;
11134 }
11135
11136 // following implemented as macro to avoid type limitations
11137 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11138
11139 void
11140 SwapEngines (int n)
11141 {   // swap settings for first engine and other engine (so far only some selected options)
11142     int h;
11143     char *p;
11144     if(n == 0) return;
11145     SWAP(directory, p)
11146     SWAP(chessProgram, p)
11147     SWAP(isUCI, h)
11148     SWAP(hasOwnBookUCI, h)
11149     SWAP(protocolVersion, h)
11150     SWAP(reuse, h)
11151     SWAP(scoreIsAbsolute, h)
11152     SWAP(timeOdds, h)
11153     SWAP(logo, p)
11154     SWAP(pgnName, p)
11155     SWAP(pvSAN, h)
11156     SWAP(engOptions, p)
11157     SWAP(engInitString, p)
11158     SWAP(computerString, p)
11159     SWAP(features, p)
11160     SWAP(fenOverride, p)
11161     SWAP(NPS, h)
11162     SWAP(accumulateTC, h)
11163     SWAP(drawDepth, h)
11164     SWAP(host, p)
11165     SWAP(pseudo, h)
11166 }
11167
11168 int
11169 GetEngineLine (char *s, int n)
11170 {
11171     int i;
11172     char buf[MSG_SIZ];
11173     extern char *icsNames;
11174     if(!s || !*s) return 0;
11175     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11176     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11177     if(!mnemonic[i]) return 0;
11178     if(n == 11) return 1; // just testing if there was a match
11179     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11180     if(n == 1) SwapEngines(n);
11181     ParseArgsFromString(buf);
11182     if(n == 1) SwapEngines(n);
11183     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11184         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11185         ParseArgsFromString(buf);
11186     }
11187     return 1;
11188 }
11189
11190 int
11191 SetPlayer (int player, char *p)
11192 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11193     int i;
11194     char buf[MSG_SIZ], *engineName;
11195     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11196     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11197     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11198     if(mnemonic[i]) {
11199         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11200         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11201         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11202         ParseArgsFromString(buf);
11203     } else { // no engine with this nickname is installed!
11204         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11205         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11206         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11207         ModeHighlight();
11208         DisplayError(buf, 0);
11209         return 0;
11210     }
11211     free(engineName);
11212     return i;
11213 }
11214
11215 char *recentEngines;
11216
11217 void
11218 RecentEngineEvent (int nr)
11219 {
11220     int n;
11221 //    SwapEngines(1); // bump first to second
11222 //    ReplaceEngine(&second, 1); // and load it there
11223     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11224     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11225     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11226         ReplaceEngine(&first, 0);
11227         FloatToFront(&appData.recentEngineList, command[n]);
11228     }
11229 }
11230
11231 int
11232 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11233 {   // determine players from game number
11234     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11235
11236     if(appData.tourneyType == 0) {
11237         roundsPerCycle = (nPlayers - 1) | 1;
11238         pairingsPerRound = nPlayers / 2;
11239     } else if(appData.tourneyType > 0) {
11240         roundsPerCycle = nPlayers - appData.tourneyType;
11241         pairingsPerRound = appData.tourneyType;
11242     }
11243     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11244     gamesPerCycle = gamesPerRound * roundsPerCycle;
11245     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11246     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11247     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11248     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11249     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11250     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11251
11252     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11253     if(appData.roundSync) *syncInterval = gamesPerRound;
11254
11255     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11256
11257     if(appData.tourneyType == 0) {
11258         if(curPairing == (nPlayers-1)/2 ) {
11259             *whitePlayer = curRound;
11260             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11261         } else {
11262             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11263             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11264             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11265             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11266         }
11267     } else if(appData.tourneyType > 1) {
11268         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11269         *whitePlayer = curRound + appData.tourneyType;
11270     } else if(appData.tourneyType > 0) {
11271         *whitePlayer = curPairing;
11272         *blackPlayer = curRound + appData.tourneyType;
11273     }
11274
11275     // take care of white/black alternation per round.
11276     // For cycles and games this is already taken care of by default, derived from matchGame!
11277     return curRound & 1;
11278 }
11279
11280 int
11281 NextTourneyGame (int nr, int *swapColors)
11282 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11283     char *p, *q;
11284     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11285     FILE *tf;
11286     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11287     tf = fopen(appData.tourneyFile, "r");
11288     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11289     ParseArgsFromFile(tf); fclose(tf);
11290     InitTimeControls(); // TC might be altered from tourney file
11291
11292     nPlayers = CountPlayers(appData.participants); // count participants
11293     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11294     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11295
11296     if(syncInterval) {
11297         p = q = appData.results;
11298         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11299         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11300             DisplayMessage(_("Waiting for other game(s)"),"");
11301             waitingForGame = TRUE;
11302             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11303             return 0;
11304         }
11305         waitingForGame = FALSE;
11306     }
11307
11308     if(appData.tourneyType < 0) {
11309         if(nr>=0 && !pairingReceived) {
11310             char buf[1<<16];
11311             if(pairing.pr == NoProc) {
11312                 if(!appData.pairingEngine[0]) {
11313                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11314                     return 0;
11315                 }
11316                 StartChessProgram(&pairing); // starts the pairing engine
11317             }
11318             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11319             SendToProgram(buf, &pairing);
11320             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11321             SendToProgram(buf, &pairing);
11322             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11323         }
11324         pairingReceived = 0;                              // ... so we continue here
11325         *swapColors = 0;
11326         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11327         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11328         matchGame = 1; roundNr = nr / syncInterval + 1;
11329     }
11330
11331     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11332
11333     // redefine engines, engine dir, etc.
11334     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11335     if(first.pr == NoProc) {
11336       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11337       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11338     }
11339     if(second.pr == NoProc) {
11340       SwapEngines(1);
11341       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11342       SwapEngines(1);         // and make that valid for second engine by swapping
11343       InitEngine(&second, 1);
11344     }
11345     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11346     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11347     return OK;
11348 }
11349
11350 void
11351 NextMatchGame ()
11352 {   // performs game initialization that does not invoke engines, and then tries to start the game
11353     int res, firstWhite, swapColors = 0;
11354     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11355     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
11356         char buf[MSG_SIZ];
11357         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11358         if(strcmp(buf, currentDebugFile)) { // name has changed
11359             FILE *f = fopen(buf, "w");
11360             if(f) { // if opening the new file failed, just keep using the old one
11361                 ASSIGN(currentDebugFile, buf);
11362                 fclose(debugFP);
11363                 debugFP = f;
11364             }
11365             if(appData.serverFileName) {
11366                 if(serverFP) fclose(serverFP);
11367                 serverFP = fopen(appData.serverFileName, "w");
11368                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11369                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11370             }
11371         }
11372     }
11373     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11374     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11375     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11376     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11377     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11378     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11379     Reset(FALSE, first.pr != NoProc);
11380     res = LoadGameOrPosition(matchGame); // setup game
11381     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11382     if(!res) return; // abort when bad game/pos file
11383     TwoMachinesEvent();
11384 }
11385
11386 void
11387 UserAdjudicationEvent (int result)
11388 {
11389     ChessMove gameResult = GameIsDrawn;
11390
11391     if( result > 0 ) {
11392         gameResult = WhiteWins;
11393     }
11394     else if( result < 0 ) {
11395         gameResult = BlackWins;
11396     }
11397
11398     if( gameMode == TwoMachinesPlay ) {
11399         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11400     }
11401 }
11402
11403
11404 // [HGM] save: calculate checksum of game to make games easily identifiable
11405 int
11406 StringCheckSum (char *s)
11407 {
11408         int i = 0;
11409         if(s==NULL) return 0;
11410         while(*s) i = i*259 + *s++;
11411         return i;
11412 }
11413
11414 int
11415 GameCheckSum ()
11416 {
11417         int i, sum=0;
11418         for(i=backwardMostMove; i<forwardMostMove; i++) {
11419                 sum += pvInfoList[i].depth;
11420                 sum += StringCheckSum(parseList[i]);
11421                 sum += StringCheckSum(commentList[i]);
11422                 sum *= 261;
11423         }
11424         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11425         return sum + StringCheckSum(commentList[i]);
11426 } // end of save patch
11427
11428 void
11429 GameEnds (ChessMove result, char *resultDetails, int whosays)
11430 {
11431     GameMode nextGameMode;
11432     int isIcsGame;
11433     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11434
11435     if(endingGame) return; /* [HGM] crash: forbid recursion */
11436     endingGame = 1;
11437     if(twoBoards) { // [HGM] dual: switch back to one board
11438         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11439         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11440     }
11441     if (appData.debugMode) {
11442       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11443               result, resultDetails ? resultDetails : "(null)", whosays);
11444     }
11445
11446     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11447
11448     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11449
11450     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11451         /* If we are playing on ICS, the server decides when the
11452            game is over, but the engine can offer to draw, claim
11453            a draw, or resign.
11454          */
11455 #if ZIPPY
11456         if (appData.zippyPlay && first.initDone) {
11457             if (result == GameIsDrawn) {
11458                 /* In case draw still needs to be claimed */
11459                 SendToICS(ics_prefix);
11460                 SendToICS("draw\n");
11461             } else if (StrCaseStr(resultDetails, "resign")) {
11462                 SendToICS(ics_prefix);
11463                 SendToICS("resign\n");
11464             }
11465         }
11466 #endif
11467         endingGame = 0; /* [HGM] crash */
11468         return;
11469     }
11470
11471     /* If we're loading the game from a file, stop */
11472     if (whosays == GE_FILE) {
11473       (void) StopLoadGameTimer();
11474       gameFileFP = NULL;
11475     }
11476
11477     /* Cancel draw offers */
11478     first.offeredDraw = second.offeredDraw = 0;
11479
11480     /* If this is an ICS game, only ICS can really say it's done;
11481        if not, anyone can. */
11482     isIcsGame = (gameMode == IcsPlayingWhite ||
11483                  gameMode == IcsPlayingBlack ||
11484                  gameMode == IcsObserving    ||
11485                  gameMode == IcsExamining);
11486
11487     if (!isIcsGame || whosays == GE_ICS) {
11488         /* OK -- not an ICS game, or ICS said it was done */
11489         StopClocks();
11490         if (!isIcsGame && !appData.noChessProgram)
11491           SetUserThinkingEnables();
11492
11493         /* [HGM] if a machine claims the game end we verify this claim */
11494         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11495             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11496                 char claimer;
11497                 ChessMove trueResult = (ChessMove) -1;
11498
11499                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11500                                             first.twoMachinesColor[0] :
11501                                             second.twoMachinesColor[0] ;
11502
11503                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11504                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11505                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11506                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11507                 } else
11508                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11509                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11510                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11511                 } else
11512                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11513                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11514                 }
11515
11516                 // now verify win claims, but not in drop games, as we don't understand those yet
11517                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11518                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11519                     (result == WhiteWins && claimer == 'w' ||
11520                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11521                       if (appData.debugMode) {
11522                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11523                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11524                       }
11525                       if(result != trueResult) {
11526                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11527                               result = claimer == 'w' ? BlackWins : WhiteWins;
11528                               resultDetails = buf;
11529                       }
11530                 } else
11531                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11532                     && (forwardMostMove <= backwardMostMove ||
11533                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11534                         (claimer=='b')==(forwardMostMove&1))
11535                                                                                   ) {
11536                       /* [HGM] verify: draws that were not flagged are false claims */
11537                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11538                       result = claimer == 'w' ? BlackWins : WhiteWins;
11539                       resultDetails = buf;
11540                 }
11541                 /* (Claiming a loss is accepted no questions asked!) */
11542             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11543                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11544                 result = GameUnfinished;
11545                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11546             }
11547             /* [HGM] bare: don't allow bare King to win */
11548             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11549                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11550                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11551                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11552                && result != GameIsDrawn)
11553             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11554                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11555                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11556                         if(p >= 0 && p <= (int)WhiteKing) k++;
11557                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11558                 }
11559                 if (appData.debugMode) {
11560                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11561                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11562                 }
11563                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11564                         result = GameIsDrawn;
11565                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11566                         resultDetails = buf;
11567                 }
11568             }
11569         }
11570
11571
11572         if(serverMoves != NULL && !loadFlag) { char c = '=';
11573             if(result==WhiteWins) c = '+';
11574             if(result==BlackWins) c = '-';
11575             if(resultDetails != NULL)
11576                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11577         }
11578         if (resultDetails != NULL) {
11579             gameInfo.result = result;
11580             gameInfo.resultDetails = StrSave(resultDetails);
11581
11582             /* display last move only if game was not loaded from file */
11583             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11584                 DisplayMove(currentMove - 1);
11585
11586             if (forwardMostMove != 0) {
11587                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11588                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11589                                                                 ) {
11590                     if (*appData.saveGameFile != NULLCHAR) {
11591                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11592                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11593                         else
11594                         SaveGameToFile(appData.saveGameFile, TRUE);
11595                     } else if (appData.autoSaveGames) {
11596                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11597                     }
11598                     if (*appData.savePositionFile != NULLCHAR) {
11599                         SavePositionToFile(appData.savePositionFile);
11600                     }
11601                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11602                 }
11603             }
11604
11605             /* Tell program how game ended in case it is learning */
11606             /* [HGM] Moved this to after saving the PGN, just in case */
11607             /* engine died and we got here through time loss. In that */
11608             /* case we will get a fatal error writing the pipe, which */
11609             /* would otherwise lose us the PGN.                       */
11610             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11611             /* output during GameEnds should never be fatal anymore   */
11612             if (gameMode == MachinePlaysWhite ||
11613                 gameMode == MachinePlaysBlack ||
11614                 gameMode == TwoMachinesPlay ||
11615                 gameMode == IcsPlayingWhite ||
11616                 gameMode == IcsPlayingBlack ||
11617                 gameMode == BeginningOfGame) {
11618                 char buf[MSG_SIZ];
11619                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11620                         resultDetails);
11621                 if (first.pr != NoProc) {
11622                     SendToProgram(buf, &first);
11623                 }
11624                 if (second.pr != NoProc &&
11625                     gameMode == TwoMachinesPlay) {
11626                     SendToProgram(buf, &second);
11627                 }
11628             }
11629         }
11630
11631         if (appData.icsActive) {
11632             if (appData.quietPlay &&
11633                 (gameMode == IcsPlayingWhite ||
11634                  gameMode == IcsPlayingBlack)) {
11635                 SendToICS(ics_prefix);
11636                 SendToICS("set shout 1\n");
11637             }
11638             nextGameMode = IcsIdle;
11639             ics_user_moved = FALSE;
11640             /* clean up premove.  It's ugly when the game has ended and the
11641              * premove highlights are still on the board.
11642              */
11643             if (gotPremove) {
11644               gotPremove = FALSE;
11645               ClearPremoveHighlights();
11646               DrawPosition(FALSE, boards[currentMove]);
11647             }
11648             if (whosays == GE_ICS) {
11649                 switch (result) {
11650                 case WhiteWins:
11651                     if (gameMode == IcsPlayingWhite)
11652                         PlayIcsWinSound();
11653                     else if(gameMode == IcsPlayingBlack)
11654                         PlayIcsLossSound();
11655                     break;
11656                 case BlackWins:
11657                     if (gameMode == IcsPlayingBlack)
11658                         PlayIcsWinSound();
11659                     else if(gameMode == IcsPlayingWhite)
11660                         PlayIcsLossSound();
11661                     break;
11662                 case GameIsDrawn:
11663                     PlayIcsDrawSound();
11664                     break;
11665                 default:
11666                     PlayIcsUnfinishedSound();
11667                 }
11668             }
11669             if(appData.quitNext) { ExitEvent(0); return; }
11670         } else if (gameMode == EditGame ||
11671                    gameMode == PlayFromGameFile ||
11672                    gameMode == AnalyzeMode ||
11673                    gameMode == AnalyzeFile) {
11674             nextGameMode = gameMode;
11675         } else {
11676             nextGameMode = EndOfGame;
11677         }
11678         pausing = FALSE;
11679         ModeHighlight();
11680     } else {
11681         nextGameMode = gameMode;
11682     }
11683
11684     if (appData.noChessProgram) {
11685         gameMode = nextGameMode;
11686         ModeHighlight();
11687         endingGame = 0; /* [HGM] crash */
11688         return;
11689     }
11690
11691     if (first.reuse) {
11692         /* Put first chess program into idle state */
11693         if (first.pr != NoProc &&
11694             (gameMode == MachinePlaysWhite ||
11695              gameMode == MachinePlaysBlack ||
11696              gameMode == TwoMachinesPlay ||
11697              gameMode == IcsPlayingWhite ||
11698              gameMode == IcsPlayingBlack ||
11699              gameMode == BeginningOfGame)) {
11700             SendToProgram("force\n", &first);
11701             if (first.usePing) {
11702               char buf[MSG_SIZ];
11703               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11704               SendToProgram(buf, &first);
11705             }
11706         }
11707     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11708         /* Kill off first chess program */
11709         if (first.isr != NULL)
11710           RemoveInputSource(first.isr);
11711         first.isr = NULL;
11712
11713         if (first.pr != NoProc) {
11714             ExitAnalyzeMode();
11715             DoSleep( appData.delayBeforeQuit );
11716             SendToProgram("quit\n", &first);
11717             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11718             first.reload = TRUE;
11719         }
11720         first.pr = NoProc;
11721     }
11722     if (second.reuse) {
11723         /* Put second chess program into idle state */
11724         if (second.pr != NoProc &&
11725             gameMode == TwoMachinesPlay) {
11726             SendToProgram("force\n", &second);
11727             if (second.usePing) {
11728               char buf[MSG_SIZ];
11729               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11730               SendToProgram(buf, &second);
11731             }
11732         }
11733     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11734         /* Kill off second chess program */
11735         if (second.isr != NULL)
11736           RemoveInputSource(second.isr);
11737         second.isr = NULL;
11738
11739         if (second.pr != NoProc) {
11740             DoSleep( appData.delayBeforeQuit );
11741             SendToProgram("quit\n", &second);
11742             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11743             second.reload = TRUE;
11744         }
11745         second.pr = NoProc;
11746     }
11747
11748     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11749         char resChar = '=';
11750         switch (result) {
11751         case WhiteWins:
11752           resChar = '+';
11753           if (first.twoMachinesColor[0] == 'w') {
11754             first.matchWins++;
11755           } else {
11756             second.matchWins++;
11757           }
11758           break;
11759         case BlackWins:
11760           resChar = '-';
11761           if (first.twoMachinesColor[0] == 'b') {
11762             first.matchWins++;
11763           } else {
11764             second.matchWins++;
11765           }
11766           break;
11767         case GameUnfinished:
11768           resChar = ' ';
11769         default:
11770           break;
11771         }
11772
11773         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11774         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11775             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11776             ReserveGame(nextGame, resChar); // sets nextGame
11777             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11778             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11779         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11780
11781         if (nextGame <= appData.matchGames && !abortMatch) {
11782             gameMode = nextGameMode;
11783             matchGame = nextGame; // this will be overruled in tourney mode!
11784             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11785             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11786             endingGame = 0; /* [HGM] crash */
11787             return;
11788         } else {
11789             gameMode = nextGameMode;
11790             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11791                      first.tidy, second.tidy,
11792                      first.matchWins, second.matchWins,
11793                      appData.matchGames - (first.matchWins + second.matchWins));
11794             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11795             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11796             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11797             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11798                 first.twoMachinesColor = "black\n";
11799                 second.twoMachinesColor = "white\n";
11800             } else {
11801                 first.twoMachinesColor = "white\n";
11802                 second.twoMachinesColor = "black\n";
11803             }
11804         }
11805     }
11806     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11807         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11808       ExitAnalyzeMode();
11809     gameMode = nextGameMode;
11810     ModeHighlight();
11811     endingGame = 0;  /* [HGM] crash */
11812     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11813         if(matchMode == TRUE) { // match through command line: exit with or without popup
11814             if(ranking) {
11815                 ToNrEvent(forwardMostMove);
11816                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11817                 else ExitEvent(0);
11818             } else DisplayFatalError(buf, 0, 0);
11819         } else { // match through menu; just stop, with or without popup
11820             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11821             ModeHighlight();
11822             if(ranking){
11823                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11824             } else DisplayNote(buf);
11825       }
11826       if(ranking) free(ranking);
11827     }
11828 }
11829
11830 /* Assumes program was just initialized (initString sent).
11831    Leaves program in force mode. */
11832 void
11833 FeedMovesToProgram (ChessProgramState *cps, int upto)
11834 {
11835     int i;
11836
11837     if (appData.debugMode)
11838       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11839               startedFromSetupPosition ? "position and " : "",
11840               backwardMostMove, upto, cps->which);
11841     if(currentlyInitializedVariant != gameInfo.variant) {
11842       char buf[MSG_SIZ];
11843         // [HGM] variantswitch: make engine aware of new variant
11844         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11845                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11846                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11847         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11848         SendToProgram(buf, cps);
11849         currentlyInitializedVariant = gameInfo.variant;
11850     }
11851     SendToProgram("force\n", cps);
11852     if (startedFromSetupPosition) {
11853         SendBoard(cps, backwardMostMove);
11854     if (appData.debugMode) {
11855         fprintf(debugFP, "feedMoves\n");
11856     }
11857     }
11858     for (i = backwardMostMove; i < upto; i++) {
11859         SendMoveToProgram(i, cps);
11860     }
11861 }
11862
11863
11864 int
11865 ResurrectChessProgram ()
11866 {
11867      /* The chess program may have exited.
11868         If so, restart it and feed it all the moves made so far. */
11869     static int doInit = 0;
11870
11871     if (appData.noChessProgram) return 1;
11872
11873     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11874         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11875         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11876         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11877     } else {
11878         if (first.pr != NoProc) return 1;
11879         StartChessProgram(&first);
11880     }
11881     InitChessProgram(&first, FALSE);
11882     FeedMovesToProgram(&first, currentMove);
11883
11884     if (!first.sendTime) {
11885         /* can't tell gnuchess what its clock should read,
11886            so we bow to its notion. */
11887         ResetClocks();
11888         timeRemaining[0][currentMove] = whiteTimeRemaining;
11889         timeRemaining[1][currentMove] = blackTimeRemaining;
11890     }
11891
11892     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11893                 appData.icsEngineAnalyze) && first.analysisSupport) {
11894       SendToProgram("analyze\n", &first);
11895       first.analyzing = TRUE;
11896     }
11897     return 1;
11898 }
11899
11900 /*
11901  * Button procedures
11902  */
11903 void
11904 Reset (int redraw, int init)
11905 {
11906     int i;
11907
11908     if (appData.debugMode) {
11909         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11910                 redraw, init, gameMode);
11911     }
11912     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11913     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11914     CleanupTail(); // [HGM] vari: delete any stored variations
11915     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11916     pausing = pauseExamInvalid = FALSE;
11917     startedFromSetupPosition = blackPlaysFirst = FALSE;
11918     firstMove = TRUE;
11919     whiteFlag = blackFlag = FALSE;
11920     userOfferedDraw = FALSE;
11921     hintRequested = bookRequested = FALSE;
11922     first.maybeThinking = FALSE;
11923     second.maybeThinking = FALSE;
11924     first.bookSuspend = FALSE; // [HGM] book
11925     second.bookSuspend = FALSE;
11926     thinkOutput[0] = NULLCHAR;
11927     lastHint[0] = NULLCHAR;
11928     ClearGameInfo(&gameInfo);
11929     gameInfo.variant = StringToVariant(appData.variant);
11930     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11931     ics_user_moved = ics_clock_paused = FALSE;
11932     ics_getting_history = H_FALSE;
11933     ics_gamenum = -1;
11934     white_holding[0] = black_holding[0] = NULLCHAR;
11935     ClearProgramStats();
11936     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11937
11938     ResetFrontEnd();
11939     ClearHighlights();
11940     flipView = appData.flipView;
11941     ClearPremoveHighlights();
11942     gotPremove = FALSE;
11943     alarmSounded = FALSE;
11944     killX = killY = -1; // [HGM] lion
11945
11946     GameEnds(EndOfFile, NULL, GE_PLAYER);
11947     if(appData.serverMovesName != NULL) {
11948         /* [HGM] prepare to make moves file for broadcasting */
11949         clock_t t = clock();
11950         if(serverMoves != NULL) fclose(serverMoves);
11951         serverMoves = fopen(appData.serverMovesName, "r");
11952         if(serverMoves != NULL) {
11953             fclose(serverMoves);
11954             /* delay 15 sec before overwriting, so all clients can see end */
11955             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11956         }
11957         serverMoves = fopen(appData.serverMovesName, "w");
11958     }
11959
11960     ExitAnalyzeMode();
11961     gameMode = BeginningOfGame;
11962     ModeHighlight();
11963     if(appData.icsActive) gameInfo.variant = VariantNormal;
11964     currentMove = forwardMostMove = backwardMostMove = 0;
11965     MarkTargetSquares(1);
11966     InitPosition(redraw);
11967     for (i = 0; i < MAX_MOVES; i++) {
11968         if (commentList[i] != NULL) {
11969             free(commentList[i]);
11970             commentList[i] = NULL;
11971         }
11972     }
11973     ResetClocks();
11974     timeRemaining[0][0] = whiteTimeRemaining;
11975     timeRemaining[1][0] = blackTimeRemaining;
11976
11977     if (first.pr == NoProc) {
11978         StartChessProgram(&first);
11979     }
11980     if (init) {
11981             InitChessProgram(&first, startedFromSetupPosition);
11982     }
11983     DisplayTitle("");
11984     DisplayMessage("", "");
11985     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11986     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11987     ClearMap();        // [HGM] exclude: invalidate map
11988 }
11989
11990 void
11991 AutoPlayGameLoop ()
11992 {
11993     for (;;) {
11994         if (!AutoPlayOneMove())
11995           return;
11996         if (matchMode || appData.timeDelay == 0)
11997           continue;
11998         if (appData.timeDelay < 0)
11999           return;
12000         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12001         break;
12002     }
12003 }
12004
12005 void
12006 AnalyzeNextGame()
12007 {
12008     ReloadGame(1); // next game
12009 }
12010
12011 int
12012 AutoPlayOneMove ()
12013 {
12014     int fromX, fromY, toX, toY;
12015
12016     if (appData.debugMode) {
12017       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12018     }
12019
12020     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12021       return FALSE;
12022
12023     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12024       pvInfoList[currentMove].depth = programStats.depth;
12025       pvInfoList[currentMove].score = programStats.score;
12026       pvInfoList[currentMove].time  = 0;
12027       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12028       else { // append analysis of final position as comment
12029         char buf[MSG_SIZ];
12030         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12031         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12032       }
12033       programStats.depth = 0;
12034     }
12035
12036     if (currentMove >= forwardMostMove) {
12037       if(gameMode == AnalyzeFile) {
12038           if(appData.loadGameIndex == -1) {
12039             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12040           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12041           } else {
12042           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12043         }
12044       }
12045 //      gameMode = EndOfGame;
12046 //      ModeHighlight();
12047
12048       /* [AS] Clear current move marker at the end of a game */
12049       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12050
12051       return FALSE;
12052     }
12053
12054     toX = moveList[currentMove][2] - AAA;
12055     toY = moveList[currentMove][3] - ONE;
12056
12057     if (moveList[currentMove][1] == '@') {
12058         if (appData.highlightLastMove) {
12059             SetHighlights(-1, -1, toX, toY);
12060         }
12061     } else {
12062         int viaX = moveList[currentMove][5] - AAA;
12063         int viaY = moveList[currentMove][6] - ONE;
12064         fromX = moveList[currentMove][0] - AAA;
12065         fromY = moveList[currentMove][1] - ONE;
12066
12067         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12068
12069         if(moveList[currentMove][4] == ';') { // multi-leg
12070             ChessSquare piece = boards[currentMove][viaY][viaX];
12071             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
12072             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
12073             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
12074             boards[currentMove][viaY][viaX] = piece;
12075         } else
12076         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12077
12078         if (appData.highlightLastMove) {
12079             SetHighlights(fromX, fromY, toX, toY);
12080         }
12081     }
12082     DisplayMove(currentMove);
12083     SendMoveToProgram(currentMove++, &first);
12084     DisplayBothClocks();
12085     DrawPosition(FALSE, boards[currentMove]);
12086     // [HGM] PV info: always display, routine tests if empty
12087     DisplayComment(currentMove - 1, commentList[currentMove]);
12088     return TRUE;
12089 }
12090
12091
12092 int
12093 LoadGameOneMove (ChessMove readAhead)
12094 {
12095     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12096     char promoChar = NULLCHAR;
12097     ChessMove moveType;
12098     char move[MSG_SIZ];
12099     char *p, *q;
12100
12101     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12102         gameMode != AnalyzeMode && gameMode != Training) {
12103         gameFileFP = NULL;
12104         return FALSE;
12105     }
12106
12107     yyboardindex = forwardMostMove;
12108     if (readAhead != EndOfFile) {
12109       moveType = readAhead;
12110     } else {
12111       if (gameFileFP == NULL)
12112           return FALSE;
12113       moveType = (ChessMove) Myylex();
12114     }
12115
12116     done = FALSE;
12117     switch (moveType) {
12118       case Comment:
12119         if (appData.debugMode)
12120           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12121         p = yy_text;
12122
12123         /* append the comment but don't display it */
12124         AppendComment(currentMove, p, FALSE);
12125         return TRUE;
12126
12127       case WhiteCapturesEnPassant:
12128       case BlackCapturesEnPassant:
12129       case WhitePromotion:
12130       case BlackPromotion:
12131       case WhiteNonPromotion:
12132       case BlackNonPromotion:
12133       case NormalMove:
12134       case FirstLeg:
12135       case WhiteKingSideCastle:
12136       case WhiteQueenSideCastle:
12137       case BlackKingSideCastle:
12138       case BlackQueenSideCastle:
12139       case WhiteKingSideCastleWild:
12140       case WhiteQueenSideCastleWild:
12141       case BlackKingSideCastleWild:
12142       case BlackQueenSideCastleWild:
12143       /* PUSH Fabien */
12144       case WhiteHSideCastleFR:
12145       case WhiteASideCastleFR:
12146       case BlackHSideCastleFR:
12147       case BlackASideCastleFR:
12148       /* POP Fabien */
12149         if (appData.debugMode)
12150           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12151         fromX = currentMoveString[0] - AAA;
12152         fromY = currentMoveString[1] - ONE;
12153         toX = currentMoveString[2] - AAA;
12154         toY = currentMoveString[3] - ONE;
12155         promoChar = currentMoveString[4];
12156         if(promoChar == ';') promoChar = currentMoveString[7];
12157         break;
12158
12159       case WhiteDrop:
12160       case BlackDrop:
12161         if (appData.debugMode)
12162           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12163         fromX = moveType == WhiteDrop ?
12164           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12165         (int) CharToPiece(ToLower(currentMoveString[0]));
12166         fromY = DROP_RANK;
12167         toX = currentMoveString[2] - AAA;
12168         toY = currentMoveString[3] - ONE;
12169         break;
12170
12171       case WhiteWins:
12172       case BlackWins:
12173       case GameIsDrawn:
12174       case GameUnfinished:
12175         if (appData.debugMode)
12176           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12177         p = strchr(yy_text, '{');
12178         if (p == NULL) p = strchr(yy_text, '(');
12179         if (p == NULL) {
12180             p = yy_text;
12181             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12182         } else {
12183             q = strchr(p, *p == '{' ? '}' : ')');
12184             if (q != NULL) *q = NULLCHAR;
12185             p++;
12186         }
12187         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12188         GameEnds(moveType, p, GE_FILE);
12189         done = TRUE;
12190         if (cmailMsgLoaded) {
12191             ClearHighlights();
12192             flipView = WhiteOnMove(currentMove);
12193             if (moveType == GameUnfinished) flipView = !flipView;
12194             if (appData.debugMode)
12195               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12196         }
12197         break;
12198
12199       case EndOfFile:
12200         if (appData.debugMode)
12201           fprintf(debugFP, "Parser hit end of file\n");
12202         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12203           case MT_NONE:
12204           case MT_CHECK:
12205             break;
12206           case MT_CHECKMATE:
12207           case MT_STAINMATE:
12208             if (WhiteOnMove(currentMove)) {
12209                 GameEnds(BlackWins, "Black mates", GE_FILE);
12210             } else {
12211                 GameEnds(WhiteWins, "White mates", GE_FILE);
12212             }
12213             break;
12214           case MT_STALEMATE:
12215             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12216             break;
12217         }
12218         done = TRUE;
12219         break;
12220
12221       case MoveNumberOne:
12222         if (lastLoadGameStart == GNUChessGame) {
12223             /* GNUChessGames have numbers, but they aren't move numbers */
12224             if (appData.debugMode)
12225               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12226                       yy_text, (int) moveType);
12227             return LoadGameOneMove(EndOfFile); /* tail recursion */
12228         }
12229         /* else fall thru */
12230
12231       case XBoardGame:
12232       case GNUChessGame:
12233       case PGNTag:
12234         /* Reached start of next game in file */
12235         if (appData.debugMode)
12236           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12237         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12238           case MT_NONE:
12239           case MT_CHECK:
12240             break;
12241           case MT_CHECKMATE:
12242           case MT_STAINMATE:
12243             if (WhiteOnMove(currentMove)) {
12244                 GameEnds(BlackWins, "Black mates", GE_FILE);
12245             } else {
12246                 GameEnds(WhiteWins, "White mates", GE_FILE);
12247             }
12248             break;
12249           case MT_STALEMATE:
12250             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12251             break;
12252         }
12253         done = TRUE;
12254         break;
12255
12256       case PositionDiagram:     /* should not happen; ignore */
12257       case ElapsedTime:         /* ignore */
12258       case NAG:                 /* ignore */
12259         if (appData.debugMode)
12260           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12261                   yy_text, (int) moveType);
12262         return LoadGameOneMove(EndOfFile); /* tail recursion */
12263
12264       case IllegalMove:
12265         if (appData.testLegality) {
12266             if (appData.debugMode)
12267               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12268             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12269                     (forwardMostMove / 2) + 1,
12270                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12271             DisplayError(move, 0);
12272             done = TRUE;
12273         } else {
12274             if (appData.debugMode)
12275               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12276                       yy_text, currentMoveString);
12277             if(currentMoveString[1] == '@') {
12278                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12279                 fromY = DROP_RANK;
12280             } else {
12281                 fromX = currentMoveString[0] - AAA;
12282                 fromY = currentMoveString[1] - ONE;
12283             }
12284             toX = currentMoveString[2] - AAA;
12285             toY = currentMoveString[3] - ONE;
12286             promoChar = currentMoveString[4];
12287         }
12288         break;
12289
12290       case AmbiguousMove:
12291         if (appData.debugMode)
12292           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12293         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12294                 (forwardMostMove / 2) + 1,
12295                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12296         DisplayError(move, 0);
12297         done = TRUE;
12298         break;
12299
12300       default:
12301       case ImpossibleMove:
12302         if (appData.debugMode)
12303           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12304         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12305                 (forwardMostMove / 2) + 1,
12306                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12307         DisplayError(move, 0);
12308         done = TRUE;
12309         break;
12310     }
12311
12312     if (done) {
12313         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12314             DrawPosition(FALSE, boards[currentMove]);
12315             DisplayBothClocks();
12316             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12317               DisplayComment(currentMove - 1, commentList[currentMove]);
12318         }
12319         (void) StopLoadGameTimer();
12320         gameFileFP = NULL;
12321         cmailOldMove = forwardMostMove;
12322         return FALSE;
12323     } else {
12324         /* currentMoveString is set as a side-effect of yylex */
12325
12326         thinkOutput[0] = NULLCHAR;
12327         MakeMove(fromX, fromY, toX, toY, promoChar);
12328         killX = killY = -1; // [HGM] lion: used up
12329         currentMove = forwardMostMove;
12330         return TRUE;
12331     }
12332 }
12333
12334 /* Load the nth game from the given file */
12335 int
12336 LoadGameFromFile (char *filename, int n, char *title, int useList)
12337 {
12338     FILE *f;
12339     char buf[MSG_SIZ];
12340
12341     if (strcmp(filename, "-") == 0) {
12342         f = stdin;
12343         title = "stdin";
12344     } else {
12345         f = fopen(filename, "rb");
12346         if (f == NULL) {
12347           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12348             DisplayError(buf, errno);
12349             return FALSE;
12350         }
12351     }
12352     if (fseek(f, 0, 0) == -1) {
12353         /* f is not seekable; probably a pipe */
12354         useList = FALSE;
12355     }
12356     if (useList && n == 0) {
12357         int error = GameListBuild(f);
12358         if (error) {
12359             DisplayError(_("Cannot build game list"), error);
12360         } else if (!ListEmpty(&gameList) &&
12361                    ((ListGame *) gameList.tailPred)->number > 1) {
12362             GameListPopUp(f, title);
12363             return TRUE;
12364         }
12365         GameListDestroy();
12366         n = 1;
12367     }
12368     if (n == 0) n = 1;
12369     return LoadGame(f, n, title, FALSE);
12370 }
12371
12372
12373 void
12374 MakeRegisteredMove ()
12375 {
12376     int fromX, fromY, toX, toY;
12377     char promoChar;
12378     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12379         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12380           case CMAIL_MOVE:
12381           case CMAIL_DRAW:
12382             if (appData.debugMode)
12383               fprintf(debugFP, "Restoring %s for game %d\n",
12384                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12385
12386             thinkOutput[0] = NULLCHAR;
12387             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12388             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12389             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12390             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12391             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12392             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12393             MakeMove(fromX, fromY, toX, toY, promoChar);
12394             ShowMove(fromX, fromY, toX, toY);
12395
12396             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12397               case MT_NONE:
12398               case MT_CHECK:
12399                 break;
12400
12401               case MT_CHECKMATE:
12402               case MT_STAINMATE:
12403                 if (WhiteOnMove(currentMove)) {
12404                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12405                 } else {
12406                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12407                 }
12408                 break;
12409
12410               case MT_STALEMATE:
12411                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12412                 break;
12413             }
12414
12415             break;
12416
12417           case CMAIL_RESIGN:
12418             if (WhiteOnMove(currentMove)) {
12419                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12420             } else {
12421                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12422             }
12423             break;
12424
12425           case CMAIL_ACCEPT:
12426             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12427             break;
12428
12429           default:
12430             break;
12431         }
12432     }
12433
12434     return;
12435 }
12436
12437 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12438 int
12439 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12440 {
12441     int retVal;
12442
12443     if (gameNumber > nCmailGames) {
12444         DisplayError(_("No more games in this message"), 0);
12445         return FALSE;
12446     }
12447     if (f == lastLoadGameFP) {
12448         int offset = gameNumber - lastLoadGameNumber;
12449         if (offset == 0) {
12450             cmailMsg[0] = NULLCHAR;
12451             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12452                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12453                 nCmailMovesRegistered--;
12454             }
12455             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12456             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12457                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12458             }
12459         } else {
12460             if (! RegisterMove()) return FALSE;
12461         }
12462     }
12463
12464     retVal = LoadGame(f, gameNumber, title, useList);
12465
12466     /* Make move registered during previous look at this game, if any */
12467     MakeRegisteredMove();
12468
12469     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12470         commentList[currentMove]
12471           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12472         DisplayComment(currentMove - 1, commentList[currentMove]);
12473     }
12474
12475     return retVal;
12476 }
12477
12478 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12479 int
12480 ReloadGame (int offset)
12481 {
12482     int gameNumber = lastLoadGameNumber + offset;
12483     if (lastLoadGameFP == NULL) {
12484         DisplayError(_("No game has been loaded yet"), 0);
12485         return FALSE;
12486     }
12487     if (gameNumber <= 0) {
12488         DisplayError(_("Can't back up any further"), 0);
12489         return FALSE;
12490     }
12491     if (cmailMsgLoaded) {
12492         return CmailLoadGame(lastLoadGameFP, gameNumber,
12493                              lastLoadGameTitle, lastLoadGameUseList);
12494     } else {
12495         return LoadGame(lastLoadGameFP, gameNumber,
12496                         lastLoadGameTitle, lastLoadGameUseList);
12497     }
12498 }
12499
12500 int keys[EmptySquare+1];
12501
12502 int
12503 PositionMatches (Board b1, Board b2)
12504 {
12505     int r, f, sum=0;
12506     switch(appData.searchMode) {
12507         case 1: return CompareWithRights(b1, b2);
12508         case 2:
12509             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12510                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12511             }
12512             return TRUE;
12513         case 3:
12514             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12515               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12516                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12517             }
12518             return sum==0;
12519         case 4:
12520             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12521                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12522             }
12523             return sum==0;
12524     }
12525     return TRUE;
12526 }
12527
12528 #define Q_PROMO  4
12529 #define Q_EP     3
12530 #define Q_BCASTL 2
12531 #define Q_WCASTL 1
12532
12533 int pieceList[256], quickBoard[256];
12534 ChessSquare pieceType[256] = { EmptySquare };
12535 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12536 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12537 int soughtTotal, turn;
12538 Boolean epOK, flipSearch;
12539
12540 typedef struct {
12541     unsigned char piece, to;
12542 } Move;
12543
12544 #define DSIZE (250000)
12545
12546 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12547 Move *moveDatabase = initialSpace;
12548 unsigned int movePtr, dataSize = DSIZE;
12549
12550 int
12551 MakePieceList (Board board, int *counts)
12552 {
12553     int r, f, n=Q_PROMO, total=0;
12554     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12555     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12556         int sq = f + (r<<4);
12557         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12558             quickBoard[sq] = ++n;
12559             pieceList[n] = sq;
12560             pieceType[n] = board[r][f];
12561             counts[board[r][f]]++;
12562             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12563             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12564             total++;
12565         }
12566     }
12567     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12568     return total;
12569 }
12570
12571 void
12572 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12573 {
12574     int sq = fromX + (fromY<<4);
12575     int piece = quickBoard[sq], rook;
12576     quickBoard[sq] = 0;
12577     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12578     if(piece == pieceList[1] && fromY == toY) {
12579       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12580         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12581         moveDatabase[movePtr++].piece = Q_WCASTL;
12582         quickBoard[sq] = piece;
12583         piece = quickBoard[from]; quickBoard[from] = 0;
12584         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12585       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12586         quickBoard[sq] = 0; // remove Rook
12587         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12588         moveDatabase[movePtr++].piece = Q_WCASTL;
12589         quickBoard[sq] = pieceList[1]; // put King
12590         piece = rook;
12591         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12592       }
12593     } else
12594     if(piece == pieceList[2] && fromY == toY) {
12595       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12596         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12597         moveDatabase[movePtr++].piece = Q_BCASTL;
12598         quickBoard[sq] = piece;
12599         piece = quickBoard[from]; quickBoard[from] = 0;
12600         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12601       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12602         quickBoard[sq] = 0; // remove Rook
12603         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12604         moveDatabase[movePtr++].piece = Q_BCASTL;
12605         quickBoard[sq] = pieceList[2]; // put King
12606         piece = rook;
12607         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12608       }
12609     } else
12610     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12611         quickBoard[(fromY<<4)+toX] = 0;
12612         moveDatabase[movePtr].piece = Q_EP;
12613         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12614         moveDatabase[movePtr].to = sq;
12615     } else
12616     if(promoPiece != pieceType[piece]) {
12617         moveDatabase[movePtr++].piece = Q_PROMO;
12618         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12619     }
12620     moveDatabase[movePtr].piece = piece;
12621     quickBoard[sq] = piece;
12622     movePtr++;
12623 }
12624
12625 int
12626 PackGame (Board board)
12627 {
12628     Move *newSpace = NULL;
12629     moveDatabase[movePtr].piece = 0; // terminate previous game
12630     if(movePtr > dataSize) {
12631         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12632         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12633         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12634         if(newSpace) {
12635             int i;
12636             Move *p = moveDatabase, *q = newSpace;
12637             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12638             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12639             moveDatabase = newSpace;
12640         } else { // calloc failed, we must be out of memory. Too bad...
12641             dataSize = 0; // prevent calloc events for all subsequent games
12642             return 0;     // and signal this one isn't cached
12643         }
12644     }
12645     movePtr++;
12646     MakePieceList(board, counts);
12647     return movePtr;
12648 }
12649
12650 int
12651 QuickCompare (Board board, int *minCounts, int *maxCounts)
12652 {   // compare according to search mode
12653     int r, f;
12654     switch(appData.searchMode)
12655     {
12656       case 1: // exact position match
12657         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12658         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12659             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12660         }
12661         break;
12662       case 2: // can have extra material on empty squares
12663         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12664             if(board[r][f] == EmptySquare) continue;
12665             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12666         }
12667         break;
12668       case 3: // material with exact Pawn structure
12669         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12670             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12671             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12672         } // fall through to material comparison
12673       case 4: // exact material
12674         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12675         break;
12676       case 6: // material range with given imbalance
12677         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12678         // fall through to range comparison
12679       case 5: // material range
12680         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12681     }
12682     return TRUE;
12683 }
12684
12685 int
12686 QuickScan (Board board, Move *move)
12687 {   // reconstruct game,and compare all positions in it
12688     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12689     do {
12690         int piece = move->piece;
12691         int to = move->to, from = pieceList[piece];
12692         if(found < 0) { // if already found just scan to game end for final piece count
12693           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12694            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12695            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12696                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12697             ) {
12698             static int lastCounts[EmptySquare+1];
12699             int i;
12700             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12701             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12702           } else stretch = 0;
12703           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12704           if(found >= 0 && !appData.minPieces) return found;
12705         }
12706         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12707           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12708           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12709             piece = (++move)->piece;
12710             from = pieceList[piece];
12711             counts[pieceType[piece]]--;
12712             pieceType[piece] = (ChessSquare) move->to;
12713             counts[move->to]++;
12714           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12715             counts[pieceType[quickBoard[to]]]--;
12716             quickBoard[to] = 0; total--;
12717             move++;
12718             continue;
12719           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12720             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12721             from  = pieceList[piece]; // so this must be King
12722             quickBoard[from] = 0;
12723             pieceList[piece] = to;
12724             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12725             quickBoard[from] = 0; // rook
12726             quickBoard[to] = piece;
12727             to = move->to; piece = move->piece;
12728             goto aftercastle;
12729           }
12730         }
12731         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12732         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12733         quickBoard[from] = 0;
12734       aftercastle:
12735         quickBoard[to] = piece;
12736         pieceList[piece] = to;
12737         cnt++; turn ^= 3;
12738         move++;
12739     } while(1);
12740 }
12741
12742 void
12743 InitSearch ()
12744 {
12745     int r, f;
12746     flipSearch = FALSE;
12747     CopyBoard(soughtBoard, boards[currentMove]);
12748     soughtTotal = MakePieceList(soughtBoard, maxSought);
12749     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12750     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12751     CopyBoard(reverseBoard, boards[currentMove]);
12752     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12753         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12754         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12755         reverseBoard[r][f] = piece;
12756     }
12757     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12758     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12759     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12760                  || (boards[currentMove][CASTLING][2] == NoRights ||
12761                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12762                  && (boards[currentMove][CASTLING][5] == NoRights ||
12763                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12764       ) {
12765         flipSearch = TRUE;
12766         CopyBoard(flipBoard, soughtBoard);
12767         CopyBoard(rotateBoard, reverseBoard);
12768         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12769             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12770             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12771         }
12772     }
12773     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12774     if(appData.searchMode >= 5) {
12775         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12776         MakePieceList(soughtBoard, minSought);
12777         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12778     }
12779     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12780         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12781 }
12782
12783 GameInfo dummyInfo;
12784 static int creatingBook;
12785
12786 int
12787 GameContainsPosition (FILE *f, ListGame *lg)
12788 {
12789     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12790     int fromX, fromY, toX, toY;
12791     char promoChar;
12792     static int initDone=FALSE;
12793
12794     // weed out games based on numerical tag comparison
12795     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12796     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12797     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12798     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12799     if(!initDone) {
12800         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12801         initDone = TRUE;
12802     }
12803     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12804     else CopyBoard(boards[scratch], initialPosition); // default start position
12805     if(lg->moves) {
12806         turn = btm + 1;
12807         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12808         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12809     }
12810     if(btm) plyNr++;
12811     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12812     fseek(f, lg->offset, 0);
12813     yynewfile(f);
12814     while(1) {
12815         yyboardindex = scratch;
12816         quickFlag = plyNr+1;
12817         next = Myylex();
12818         quickFlag = 0;
12819         switch(next) {
12820             case PGNTag:
12821                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12822             default:
12823                 continue;
12824
12825             case XBoardGame:
12826             case GNUChessGame:
12827                 if(plyNr) return -1; // after we have seen moves, this is for new game
12828               continue;
12829
12830             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12831             case ImpossibleMove:
12832             case WhiteWins: // game ends here with these four
12833             case BlackWins:
12834             case GameIsDrawn:
12835             case GameUnfinished:
12836                 return -1;
12837
12838             case IllegalMove:
12839                 if(appData.testLegality) return -1;
12840             case WhiteCapturesEnPassant:
12841             case BlackCapturesEnPassant:
12842             case WhitePromotion:
12843             case BlackPromotion:
12844             case WhiteNonPromotion:
12845             case BlackNonPromotion:
12846             case NormalMove:
12847             case FirstLeg:
12848             case WhiteKingSideCastle:
12849             case WhiteQueenSideCastle:
12850             case BlackKingSideCastle:
12851             case BlackQueenSideCastle:
12852             case WhiteKingSideCastleWild:
12853             case WhiteQueenSideCastleWild:
12854             case BlackKingSideCastleWild:
12855             case BlackQueenSideCastleWild:
12856             case WhiteHSideCastleFR:
12857             case WhiteASideCastleFR:
12858             case BlackHSideCastleFR:
12859             case BlackASideCastleFR:
12860                 fromX = currentMoveString[0] - AAA;
12861                 fromY = currentMoveString[1] - ONE;
12862                 toX = currentMoveString[2] - AAA;
12863                 toY = currentMoveString[3] - ONE;
12864                 promoChar = currentMoveString[4];
12865                 break;
12866             case WhiteDrop:
12867             case BlackDrop:
12868                 fromX = next == WhiteDrop ?
12869                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12870                   (int) CharToPiece(ToLower(currentMoveString[0]));
12871                 fromY = DROP_RANK;
12872                 toX = currentMoveString[2] - AAA;
12873                 toY = currentMoveString[3] - ONE;
12874                 promoChar = 0;
12875                 break;
12876         }
12877         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12878         plyNr++;
12879         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12880         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12881         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12882         if(appData.findMirror) {
12883             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12884             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12885         }
12886     }
12887 }
12888
12889 /* Load the nth game from open file f */
12890 int
12891 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12892 {
12893     ChessMove cm;
12894     char buf[MSG_SIZ];
12895     int gn = gameNumber;
12896     ListGame *lg = NULL;
12897     int numPGNTags = 0;
12898     int err, pos = -1;
12899     GameMode oldGameMode;
12900     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12901     char oldName[MSG_SIZ];
12902
12903     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12904
12905     if (appData.debugMode)
12906         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12907
12908     if (gameMode == Training )
12909         SetTrainingModeOff();
12910
12911     oldGameMode = gameMode;
12912     if (gameMode != BeginningOfGame) {
12913       Reset(FALSE, TRUE);
12914     }
12915     killX = killY = -1; // [HGM] lion: in case we did not Reset
12916
12917     gameFileFP = f;
12918     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12919         fclose(lastLoadGameFP);
12920     }
12921
12922     if (useList) {
12923         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12924
12925         if (lg) {
12926             fseek(f, lg->offset, 0);
12927             GameListHighlight(gameNumber);
12928             pos = lg->position;
12929             gn = 1;
12930         }
12931         else {
12932             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12933               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12934             else
12935             DisplayError(_("Game number out of range"), 0);
12936             return FALSE;
12937         }
12938     } else {
12939         GameListDestroy();
12940         if (fseek(f, 0, 0) == -1) {
12941             if (f == lastLoadGameFP ?
12942                 gameNumber == lastLoadGameNumber + 1 :
12943                 gameNumber == 1) {
12944                 gn = 1;
12945             } else {
12946                 DisplayError(_("Can't seek on game file"), 0);
12947                 return FALSE;
12948             }
12949         }
12950     }
12951     lastLoadGameFP = f;
12952     lastLoadGameNumber = gameNumber;
12953     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12954     lastLoadGameUseList = useList;
12955
12956     yynewfile(f);
12957
12958     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12959       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12960                 lg->gameInfo.black);
12961             DisplayTitle(buf);
12962     } else if (*title != NULLCHAR) {
12963         if (gameNumber > 1) {
12964           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12965             DisplayTitle(buf);
12966         } else {
12967             DisplayTitle(title);
12968         }
12969     }
12970
12971     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12972         gameMode = PlayFromGameFile;
12973         ModeHighlight();
12974     }
12975
12976     currentMove = forwardMostMove = backwardMostMove = 0;
12977     CopyBoard(boards[0], initialPosition);
12978     StopClocks();
12979
12980     /*
12981      * Skip the first gn-1 games in the file.
12982      * Also skip over anything that precedes an identifiable
12983      * start of game marker, to avoid being confused by
12984      * garbage at the start of the file.  Currently
12985      * recognized start of game markers are the move number "1",
12986      * the pattern "gnuchess .* game", the pattern
12987      * "^[#;%] [^ ]* game file", and a PGN tag block.
12988      * A game that starts with one of the latter two patterns
12989      * will also have a move number 1, possibly
12990      * following a position diagram.
12991      * 5-4-02: Let's try being more lenient and allowing a game to
12992      * start with an unnumbered move.  Does that break anything?
12993      */
12994     cm = lastLoadGameStart = EndOfFile;
12995     while (gn > 0) {
12996         yyboardindex = forwardMostMove;
12997         cm = (ChessMove) Myylex();
12998         switch (cm) {
12999           case EndOfFile:
13000             if (cmailMsgLoaded) {
13001                 nCmailGames = CMAIL_MAX_GAMES - gn;
13002             } else {
13003                 Reset(TRUE, TRUE);
13004                 DisplayError(_("Game not found in file"), 0);
13005             }
13006             return FALSE;
13007
13008           case GNUChessGame:
13009           case XBoardGame:
13010             gn--;
13011             lastLoadGameStart = cm;
13012             break;
13013
13014           case MoveNumberOne:
13015             switch (lastLoadGameStart) {
13016               case GNUChessGame:
13017               case XBoardGame:
13018               case PGNTag:
13019                 break;
13020               case MoveNumberOne:
13021               case EndOfFile:
13022                 gn--;           /* count this game */
13023                 lastLoadGameStart = cm;
13024                 break;
13025               default:
13026                 /* impossible */
13027                 break;
13028             }
13029             break;
13030
13031           case PGNTag:
13032             switch (lastLoadGameStart) {
13033               case GNUChessGame:
13034               case PGNTag:
13035               case MoveNumberOne:
13036               case EndOfFile:
13037                 gn--;           /* count this game */
13038                 lastLoadGameStart = cm;
13039                 break;
13040               case XBoardGame:
13041                 lastLoadGameStart = cm; /* game counted already */
13042                 break;
13043               default:
13044                 /* impossible */
13045                 break;
13046             }
13047             if (gn > 0) {
13048                 do {
13049                     yyboardindex = forwardMostMove;
13050                     cm = (ChessMove) Myylex();
13051                 } while (cm == PGNTag || cm == Comment);
13052             }
13053             break;
13054
13055           case WhiteWins:
13056           case BlackWins:
13057           case GameIsDrawn:
13058             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13059                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13060                     != CMAIL_OLD_RESULT) {
13061                     nCmailResults ++ ;
13062                     cmailResult[  CMAIL_MAX_GAMES
13063                                 - gn - 1] = CMAIL_OLD_RESULT;
13064                 }
13065             }
13066             break;
13067
13068           case NormalMove:
13069           case FirstLeg:
13070             /* Only a NormalMove can be at the start of a game
13071              * without a position diagram. */
13072             if (lastLoadGameStart == EndOfFile ) {
13073               gn--;
13074               lastLoadGameStart = MoveNumberOne;
13075             }
13076             break;
13077
13078           default:
13079             break;
13080         }
13081     }
13082
13083     if (appData.debugMode)
13084       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13085
13086     if (cm == XBoardGame) {
13087         /* Skip any header junk before position diagram and/or move 1 */
13088         for (;;) {
13089             yyboardindex = forwardMostMove;
13090             cm = (ChessMove) Myylex();
13091
13092             if (cm == EndOfFile ||
13093                 cm == GNUChessGame || cm == XBoardGame) {
13094                 /* Empty game; pretend end-of-file and handle later */
13095                 cm = EndOfFile;
13096                 break;
13097             }
13098
13099             if (cm == MoveNumberOne || cm == PositionDiagram ||
13100                 cm == PGNTag || cm == Comment)
13101               break;
13102         }
13103     } else if (cm == GNUChessGame) {
13104         if (gameInfo.event != NULL) {
13105             free(gameInfo.event);
13106         }
13107         gameInfo.event = StrSave(yy_text);
13108     }
13109
13110     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13111     while (cm == PGNTag) {
13112         if (appData.debugMode)
13113           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13114         err = ParsePGNTag(yy_text, &gameInfo);
13115         if (!err) numPGNTags++;
13116
13117         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13118         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13119             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13120             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13121             InitPosition(TRUE);
13122             oldVariant = gameInfo.variant;
13123             if (appData.debugMode)
13124               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13125         }
13126
13127
13128         if (gameInfo.fen != NULL) {
13129           Board initial_position;
13130           startedFromSetupPosition = TRUE;
13131           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13132             Reset(TRUE, TRUE);
13133             DisplayError(_("Bad FEN position in file"), 0);
13134             return FALSE;
13135           }
13136           CopyBoard(boards[0], initial_position);
13137           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13138             CopyBoard(initialPosition, initial_position);
13139           if (blackPlaysFirst) {
13140             currentMove = forwardMostMove = backwardMostMove = 1;
13141             CopyBoard(boards[1], initial_position);
13142             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13143             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13144             timeRemaining[0][1] = whiteTimeRemaining;
13145             timeRemaining[1][1] = blackTimeRemaining;
13146             if (commentList[0] != NULL) {
13147               commentList[1] = commentList[0];
13148               commentList[0] = NULL;
13149             }
13150           } else {
13151             currentMove = forwardMostMove = backwardMostMove = 0;
13152           }
13153           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13154           {   int i;
13155               initialRulePlies = FENrulePlies;
13156               for( i=0; i< nrCastlingRights; i++ )
13157                   initialRights[i] = initial_position[CASTLING][i];
13158           }
13159           yyboardindex = forwardMostMove;
13160           free(gameInfo.fen);
13161           gameInfo.fen = NULL;
13162         }
13163
13164         yyboardindex = forwardMostMove;
13165         cm = (ChessMove) Myylex();
13166
13167         /* Handle comments interspersed among the tags */
13168         while (cm == Comment) {
13169             char *p;
13170             if (appData.debugMode)
13171               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13172             p = yy_text;
13173             AppendComment(currentMove, p, FALSE);
13174             yyboardindex = forwardMostMove;
13175             cm = (ChessMove) Myylex();
13176         }
13177     }
13178
13179     /* don't rely on existence of Event tag since if game was
13180      * pasted from clipboard the Event tag may not exist
13181      */
13182     if (numPGNTags > 0){
13183         char *tags;
13184         if (gameInfo.variant == VariantNormal) {
13185           VariantClass v = StringToVariant(gameInfo.event);
13186           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13187           if(v < VariantShogi) gameInfo.variant = v;
13188         }
13189         if (!matchMode) {
13190           if( appData.autoDisplayTags ) {
13191             tags = PGNTags(&gameInfo);
13192             TagsPopUp(tags, CmailMsg());
13193             free(tags);
13194           }
13195         }
13196     } else {
13197         /* Make something up, but don't display it now */
13198         SetGameInfo();
13199         TagsPopDown();
13200     }
13201
13202     if (cm == PositionDiagram) {
13203         int i, j;
13204         char *p;
13205         Board initial_position;
13206
13207         if (appData.debugMode)
13208           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13209
13210         if (!startedFromSetupPosition) {
13211             p = yy_text;
13212             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13213               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13214                 switch (*p) {
13215                   case '{':
13216                   case '[':
13217                   case '-':
13218                   case ' ':
13219                   case '\t':
13220                   case '\n':
13221                   case '\r':
13222                     break;
13223                   default:
13224                     initial_position[i][j++] = CharToPiece(*p);
13225                     break;
13226                 }
13227             while (*p == ' ' || *p == '\t' ||
13228                    *p == '\n' || *p == '\r') p++;
13229
13230             if (strncmp(p, "black", strlen("black"))==0)
13231               blackPlaysFirst = TRUE;
13232             else
13233               blackPlaysFirst = FALSE;
13234             startedFromSetupPosition = TRUE;
13235
13236             CopyBoard(boards[0], initial_position);
13237             if (blackPlaysFirst) {
13238                 currentMove = forwardMostMove = backwardMostMove = 1;
13239                 CopyBoard(boards[1], initial_position);
13240                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13241                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13242                 timeRemaining[0][1] = whiteTimeRemaining;
13243                 timeRemaining[1][1] = blackTimeRemaining;
13244                 if (commentList[0] != NULL) {
13245                     commentList[1] = commentList[0];
13246                     commentList[0] = NULL;
13247                 }
13248             } else {
13249                 currentMove = forwardMostMove = backwardMostMove = 0;
13250             }
13251         }
13252         yyboardindex = forwardMostMove;
13253         cm = (ChessMove) Myylex();
13254     }
13255
13256   if(!creatingBook) {
13257     if (first.pr == NoProc) {
13258         StartChessProgram(&first);
13259     }
13260     InitChessProgram(&first, FALSE);
13261     if(gameInfo.variant == VariantUnknown && *oldName) {
13262         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13263         gameInfo.variant = v;
13264     }
13265     SendToProgram("force\n", &first);
13266     if (startedFromSetupPosition) {
13267         SendBoard(&first, forwardMostMove);
13268     if (appData.debugMode) {
13269         fprintf(debugFP, "Load Game\n");
13270     }
13271         DisplayBothClocks();
13272     }
13273   }
13274
13275     /* [HGM] server: flag to write setup moves in broadcast file as one */
13276     loadFlag = appData.suppressLoadMoves;
13277
13278     while (cm == Comment) {
13279         char *p;
13280         if (appData.debugMode)
13281           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13282         p = yy_text;
13283         AppendComment(currentMove, p, FALSE);
13284         yyboardindex = forwardMostMove;
13285         cm = (ChessMove) Myylex();
13286     }
13287
13288     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13289         cm == WhiteWins || cm == BlackWins ||
13290         cm == GameIsDrawn || cm == GameUnfinished) {
13291         DisplayMessage("", _("No moves in game"));
13292         if (cmailMsgLoaded) {
13293             if (appData.debugMode)
13294               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13295             ClearHighlights();
13296             flipView = FALSE;
13297         }
13298         DrawPosition(FALSE, boards[currentMove]);
13299         DisplayBothClocks();
13300         gameMode = EditGame;
13301         ModeHighlight();
13302         gameFileFP = NULL;
13303         cmailOldMove = 0;
13304         return TRUE;
13305     }
13306
13307     // [HGM] PV info: routine tests if comment empty
13308     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13309         DisplayComment(currentMove - 1, commentList[currentMove]);
13310     }
13311     if (!matchMode && appData.timeDelay != 0)
13312       DrawPosition(FALSE, boards[currentMove]);
13313
13314     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13315       programStats.ok_to_send = 1;
13316     }
13317
13318     /* if the first token after the PGN tags is a move
13319      * and not move number 1, retrieve it from the parser
13320      */
13321     if (cm != MoveNumberOne)
13322         LoadGameOneMove(cm);
13323
13324     /* load the remaining moves from the file */
13325     while (LoadGameOneMove(EndOfFile)) {
13326       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13327       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13328     }
13329
13330     /* rewind to the start of the game */
13331     currentMove = backwardMostMove;
13332
13333     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13334
13335     if (oldGameMode == AnalyzeFile) {
13336       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13337       AnalyzeFileEvent();
13338     } else
13339     if (oldGameMode == AnalyzeMode) {
13340       AnalyzeFileEvent();
13341     }
13342
13343     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13344         long int w, b; // [HGM] adjourn: restore saved clock times
13345         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13346         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13347             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13348             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13349         }
13350     }
13351
13352     if(creatingBook) return TRUE;
13353     if (!matchMode && pos > 0) {
13354         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13355     } else
13356     if (matchMode || appData.timeDelay == 0) {
13357       ToEndEvent();
13358     } else if (appData.timeDelay > 0) {
13359       AutoPlayGameLoop();
13360     }
13361
13362     if (appData.debugMode)
13363         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13364
13365     loadFlag = 0; /* [HGM] true game starts */
13366     return TRUE;
13367 }
13368
13369 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13370 int
13371 ReloadPosition (int offset)
13372 {
13373     int positionNumber = lastLoadPositionNumber + offset;
13374     if (lastLoadPositionFP == NULL) {
13375         DisplayError(_("No position has been loaded yet"), 0);
13376         return FALSE;
13377     }
13378     if (positionNumber <= 0) {
13379         DisplayError(_("Can't back up any further"), 0);
13380         return FALSE;
13381     }
13382     return LoadPosition(lastLoadPositionFP, positionNumber,
13383                         lastLoadPositionTitle);
13384 }
13385
13386 /* Load the nth position from the given file */
13387 int
13388 LoadPositionFromFile (char *filename, int n, char *title)
13389 {
13390     FILE *f;
13391     char buf[MSG_SIZ];
13392
13393     if (strcmp(filename, "-") == 0) {
13394         return LoadPosition(stdin, n, "stdin");
13395     } else {
13396         f = fopen(filename, "rb");
13397         if (f == NULL) {
13398             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13399             DisplayError(buf, errno);
13400             return FALSE;
13401         } else {
13402             return LoadPosition(f, n, title);
13403         }
13404     }
13405 }
13406
13407 /* Load the nth position from the given open file, and close it */
13408 int
13409 LoadPosition (FILE *f, int positionNumber, char *title)
13410 {
13411     char *p, line[MSG_SIZ];
13412     Board initial_position;
13413     int i, j, fenMode, pn;
13414
13415     if (gameMode == Training )
13416         SetTrainingModeOff();
13417
13418     if (gameMode != BeginningOfGame) {
13419         Reset(FALSE, TRUE);
13420     }
13421     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13422         fclose(lastLoadPositionFP);
13423     }
13424     if (positionNumber == 0) positionNumber = 1;
13425     lastLoadPositionFP = f;
13426     lastLoadPositionNumber = positionNumber;
13427     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13428     if (first.pr == NoProc && !appData.noChessProgram) {
13429       StartChessProgram(&first);
13430       InitChessProgram(&first, FALSE);
13431     }
13432     pn = positionNumber;
13433     if (positionNumber < 0) {
13434         /* Negative position number means to seek to that byte offset */
13435         if (fseek(f, -positionNumber, 0) == -1) {
13436             DisplayError(_("Can't seek on position file"), 0);
13437             return FALSE;
13438         };
13439         pn = 1;
13440     } else {
13441         if (fseek(f, 0, 0) == -1) {
13442             if (f == lastLoadPositionFP ?
13443                 positionNumber == lastLoadPositionNumber + 1 :
13444                 positionNumber == 1) {
13445                 pn = 1;
13446             } else {
13447                 DisplayError(_("Can't seek on position file"), 0);
13448                 return FALSE;
13449             }
13450         }
13451     }
13452     /* See if this file is FEN or old-style xboard */
13453     if (fgets(line, MSG_SIZ, f) == NULL) {
13454         DisplayError(_("Position not found in file"), 0);
13455         return FALSE;
13456     }
13457     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13458     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13459
13460     if (pn >= 2) {
13461         if (fenMode || line[0] == '#') pn--;
13462         while (pn > 0) {
13463             /* skip positions before number pn */
13464             if (fgets(line, MSG_SIZ, f) == NULL) {
13465                 Reset(TRUE, TRUE);
13466                 DisplayError(_("Position not found in file"), 0);
13467                 return FALSE;
13468             }
13469             if (fenMode || line[0] == '#') pn--;
13470         }
13471     }
13472
13473     if (fenMode) {
13474         char *p;
13475         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13476             DisplayError(_("Bad FEN position in file"), 0);
13477             return FALSE;
13478         }
13479         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13480             sscanf(p+3, "%s", bestMove);
13481         } else *bestMove = NULLCHAR;
13482     } else {
13483         (void) fgets(line, MSG_SIZ, f);
13484         (void) fgets(line, MSG_SIZ, f);
13485
13486         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13487             (void) fgets(line, MSG_SIZ, f);
13488             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13489                 if (*p == ' ')
13490                   continue;
13491                 initial_position[i][j++] = CharToPiece(*p);
13492             }
13493         }
13494
13495         blackPlaysFirst = FALSE;
13496         if (!feof(f)) {
13497             (void) fgets(line, MSG_SIZ, f);
13498             if (strncmp(line, "black", strlen("black"))==0)
13499               blackPlaysFirst = TRUE;
13500         }
13501     }
13502     startedFromSetupPosition = TRUE;
13503
13504     CopyBoard(boards[0], initial_position);
13505     if (blackPlaysFirst) {
13506         currentMove = forwardMostMove = backwardMostMove = 1;
13507         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13508         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13509         CopyBoard(boards[1], initial_position);
13510         DisplayMessage("", _("Black to play"));
13511     } else {
13512         currentMove = forwardMostMove = backwardMostMove = 0;
13513         DisplayMessage("", _("White to play"));
13514     }
13515     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13516     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13517         SendToProgram("force\n", &first);
13518         SendBoard(&first, forwardMostMove);
13519     }
13520     if (appData.debugMode) {
13521 int i, j;
13522   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13523   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13524         fprintf(debugFP, "Load Position\n");
13525     }
13526
13527     if (positionNumber > 1) {
13528       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13529         DisplayTitle(line);
13530     } else {
13531         DisplayTitle(title);
13532     }
13533     gameMode = EditGame;
13534     ModeHighlight();
13535     ResetClocks();
13536     timeRemaining[0][1] = whiteTimeRemaining;
13537     timeRemaining[1][1] = blackTimeRemaining;
13538     DrawPosition(FALSE, boards[currentMove]);
13539
13540     return TRUE;
13541 }
13542
13543
13544 void
13545 CopyPlayerNameIntoFileName (char **dest, char *src)
13546 {
13547     while (*src != NULLCHAR && *src != ',') {
13548         if (*src == ' ') {
13549             *(*dest)++ = '_';
13550             src++;
13551         } else {
13552             *(*dest)++ = *src++;
13553         }
13554     }
13555 }
13556
13557 char *
13558 DefaultFileName (char *ext)
13559 {
13560     static char def[MSG_SIZ];
13561     char *p;
13562
13563     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13564         p = def;
13565         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13566         *p++ = '-';
13567         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13568         *p++ = '.';
13569         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13570     } else {
13571         def[0] = NULLCHAR;
13572     }
13573     return def;
13574 }
13575
13576 /* Save the current game to the given file */
13577 int
13578 SaveGameToFile (char *filename, int append)
13579 {
13580     FILE *f;
13581     char buf[MSG_SIZ];
13582     int result, i, t,tot=0;
13583
13584     if (strcmp(filename, "-") == 0) {
13585         return SaveGame(stdout, 0, NULL);
13586     } else {
13587         for(i=0; i<10; i++) { // upto 10 tries
13588              f = fopen(filename, append ? "a" : "w");
13589              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13590              if(f || errno != 13) break;
13591              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13592              tot += t;
13593         }
13594         if (f == NULL) {
13595             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13596             DisplayError(buf, errno);
13597             return FALSE;
13598         } else {
13599             safeStrCpy(buf, lastMsg, MSG_SIZ);
13600             DisplayMessage(_("Waiting for access to save file"), "");
13601             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13602             DisplayMessage(_("Saving game"), "");
13603             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13604             result = SaveGame(f, 0, NULL);
13605             DisplayMessage(buf, "");
13606             return result;
13607         }
13608     }
13609 }
13610
13611 char *
13612 SavePart (char *str)
13613 {
13614     static char buf[MSG_SIZ];
13615     char *p;
13616
13617     p = strchr(str, ' ');
13618     if (p == NULL) return str;
13619     strncpy(buf, str, p - str);
13620     buf[p - str] = NULLCHAR;
13621     return buf;
13622 }
13623
13624 #define PGN_MAX_LINE 75
13625
13626 #define PGN_SIDE_WHITE  0
13627 #define PGN_SIDE_BLACK  1
13628
13629 static int
13630 FindFirstMoveOutOfBook (int side)
13631 {
13632     int result = -1;
13633
13634     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13635         int index = backwardMostMove;
13636         int has_book_hit = 0;
13637
13638         if( (index % 2) != side ) {
13639             index++;
13640         }
13641
13642         while( index < forwardMostMove ) {
13643             /* Check to see if engine is in book */
13644             int depth = pvInfoList[index].depth;
13645             int score = pvInfoList[index].score;
13646             int in_book = 0;
13647
13648             if( depth <= 2 ) {
13649                 in_book = 1;
13650             }
13651             else if( score == 0 && depth == 63 ) {
13652                 in_book = 1; /* Zappa */
13653             }
13654             else if( score == 2 && depth == 99 ) {
13655                 in_book = 1; /* Abrok */
13656             }
13657
13658             has_book_hit += in_book;
13659
13660             if( ! in_book ) {
13661                 result = index;
13662
13663                 break;
13664             }
13665
13666             index += 2;
13667         }
13668     }
13669
13670     return result;
13671 }
13672
13673 void
13674 GetOutOfBookInfo (char * buf)
13675 {
13676     int oob[2];
13677     int i;
13678     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13679
13680     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13681     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13682
13683     *buf = '\0';
13684
13685     if( oob[0] >= 0 || oob[1] >= 0 ) {
13686         for( i=0; i<2; i++ ) {
13687             int idx = oob[i];
13688
13689             if( idx >= 0 ) {
13690                 if( i > 0 && oob[0] >= 0 ) {
13691                     strcat( buf, "   " );
13692                 }
13693
13694                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13695                 sprintf( buf+strlen(buf), "%s%.2f",
13696                     pvInfoList[idx].score >= 0 ? "+" : "",
13697                     pvInfoList[idx].score / 100.0 );
13698             }
13699         }
13700     }
13701 }
13702
13703 /* Save game in PGN style */
13704 static void
13705 SaveGamePGN2 (FILE *f)
13706 {
13707     int i, offset, linelen, newblock;
13708 //    char *movetext;
13709     char numtext[32];
13710     int movelen, numlen, blank;
13711     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13712
13713     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13714
13715     PrintPGNTags(f, &gameInfo);
13716
13717     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13718
13719     if (backwardMostMove > 0 || startedFromSetupPosition) {
13720         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13721         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13722         fprintf(f, "\n{--------------\n");
13723         PrintPosition(f, backwardMostMove);
13724         fprintf(f, "--------------}\n");
13725         free(fen);
13726     }
13727     else {
13728         /* [AS] Out of book annotation */
13729         if( appData.saveOutOfBookInfo ) {
13730             char buf[64];
13731
13732             GetOutOfBookInfo( buf );
13733
13734             if( buf[0] != '\0' ) {
13735                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13736             }
13737         }
13738
13739         fprintf(f, "\n");
13740     }
13741
13742     i = backwardMostMove;
13743     linelen = 0;
13744     newblock = TRUE;
13745
13746     while (i < forwardMostMove) {
13747         /* Print comments preceding this move */
13748         if (commentList[i] != NULL) {
13749             if (linelen > 0) fprintf(f, "\n");
13750             fprintf(f, "%s", commentList[i]);
13751             linelen = 0;
13752             newblock = TRUE;
13753         }
13754
13755         /* Format move number */
13756         if ((i % 2) == 0)
13757           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13758         else
13759           if (newblock)
13760             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13761           else
13762             numtext[0] = NULLCHAR;
13763
13764         numlen = strlen(numtext);
13765         newblock = FALSE;
13766
13767         /* Print move number */
13768         blank = linelen > 0 && numlen > 0;
13769         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13770             fprintf(f, "\n");
13771             linelen = 0;
13772             blank = 0;
13773         }
13774         if (blank) {
13775             fprintf(f, " ");
13776             linelen++;
13777         }
13778         fprintf(f, "%s", numtext);
13779         linelen += numlen;
13780
13781         /* Get move */
13782         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13783         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13784
13785         /* Print move */
13786         blank = linelen > 0 && movelen > 0;
13787         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13788             fprintf(f, "\n");
13789             linelen = 0;
13790             blank = 0;
13791         }
13792         if (blank) {
13793             fprintf(f, " ");
13794             linelen++;
13795         }
13796         fprintf(f, "%s", move_buffer);
13797         linelen += movelen;
13798
13799         /* [AS] Add PV info if present */
13800         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13801             /* [HGM] add time */
13802             char buf[MSG_SIZ]; int seconds;
13803
13804             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13805
13806             if( seconds <= 0)
13807               buf[0] = 0;
13808             else
13809               if( seconds < 30 )
13810                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13811               else
13812                 {
13813                   seconds = (seconds + 4)/10; // round to full seconds
13814                   if( seconds < 60 )
13815                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13816                   else
13817                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13818                 }
13819
13820             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13821                       pvInfoList[i].score >= 0 ? "+" : "",
13822                       pvInfoList[i].score / 100.0,
13823                       pvInfoList[i].depth,
13824                       buf );
13825
13826             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13827
13828             /* Print score/depth */
13829             blank = linelen > 0 && movelen > 0;
13830             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13831                 fprintf(f, "\n");
13832                 linelen = 0;
13833                 blank = 0;
13834             }
13835             if (blank) {
13836                 fprintf(f, " ");
13837                 linelen++;
13838             }
13839             fprintf(f, "%s", move_buffer);
13840             linelen += movelen;
13841         }
13842
13843         i++;
13844     }
13845
13846     /* Start a new line */
13847     if (linelen > 0) fprintf(f, "\n");
13848
13849     /* Print comments after last move */
13850     if (commentList[i] != NULL) {
13851         fprintf(f, "%s\n", commentList[i]);
13852     }
13853
13854     /* Print result */
13855     if (gameInfo.resultDetails != NULL &&
13856         gameInfo.resultDetails[0] != NULLCHAR) {
13857         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13858         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13859            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13860             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13861         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13862     } else {
13863         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13864     }
13865 }
13866
13867 /* Save game in PGN style and close the file */
13868 int
13869 SaveGamePGN (FILE *f)
13870 {
13871     SaveGamePGN2(f);
13872     fclose(f);
13873     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13874     return TRUE;
13875 }
13876
13877 /* Save game in old style and close the file */
13878 int
13879 SaveGameOldStyle (FILE *f)
13880 {
13881     int i, offset;
13882     time_t tm;
13883
13884     tm = time((time_t *) NULL);
13885
13886     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13887     PrintOpponents(f);
13888
13889     if (backwardMostMove > 0 || startedFromSetupPosition) {
13890         fprintf(f, "\n[--------------\n");
13891         PrintPosition(f, backwardMostMove);
13892         fprintf(f, "--------------]\n");
13893     } else {
13894         fprintf(f, "\n");
13895     }
13896
13897     i = backwardMostMove;
13898     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13899
13900     while (i < forwardMostMove) {
13901         if (commentList[i] != NULL) {
13902             fprintf(f, "[%s]\n", commentList[i]);
13903         }
13904
13905         if ((i % 2) == 1) {
13906             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13907             i++;
13908         } else {
13909             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13910             i++;
13911             if (commentList[i] != NULL) {
13912                 fprintf(f, "\n");
13913                 continue;
13914             }
13915             if (i >= forwardMostMove) {
13916                 fprintf(f, "\n");
13917                 break;
13918             }
13919             fprintf(f, "%s\n", parseList[i]);
13920             i++;
13921         }
13922     }
13923
13924     if (commentList[i] != NULL) {
13925         fprintf(f, "[%s]\n", commentList[i]);
13926     }
13927
13928     /* This isn't really the old style, but it's close enough */
13929     if (gameInfo.resultDetails != NULL &&
13930         gameInfo.resultDetails[0] != NULLCHAR) {
13931         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13932                 gameInfo.resultDetails);
13933     } else {
13934         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13935     }
13936
13937     fclose(f);
13938     return TRUE;
13939 }
13940
13941 /* Save the current game to open file f and close the file */
13942 int
13943 SaveGame (FILE *f, int dummy, char *dummy2)
13944 {
13945     if (gameMode == EditPosition) EditPositionDone(TRUE);
13946     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13947     if (appData.oldSaveStyle)
13948       return SaveGameOldStyle(f);
13949     else
13950       return SaveGamePGN(f);
13951 }
13952
13953 /* Save the current position to the given file */
13954 int
13955 SavePositionToFile (char *filename)
13956 {
13957     FILE *f;
13958     char buf[MSG_SIZ];
13959
13960     if (strcmp(filename, "-") == 0) {
13961         return SavePosition(stdout, 0, NULL);
13962     } else {
13963         f = fopen(filename, "a");
13964         if (f == NULL) {
13965             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13966             DisplayError(buf, errno);
13967             return FALSE;
13968         } else {
13969             safeStrCpy(buf, lastMsg, MSG_SIZ);
13970             DisplayMessage(_("Waiting for access to save file"), "");
13971             flock(fileno(f), LOCK_EX); // [HGM] lock
13972             DisplayMessage(_("Saving position"), "");
13973             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13974             SavePosition(f, 0, NULL);
13975             DisplayMessage(buf, "");
13976             return TRUE;
13977         }
13978     }
13979 }
13980
13981 /* Save the current position to the given open file and close the file */
13982 int
13983 SavePosition (FILE *f, int dummy, char *dummy2)
13984 {
13985     time_t tm;
13986     char *fen;
13987
13988     if (gameMode == EditPosition) EditPositionDone(TRUE);
13989     if (appData.oldSaveStyle) {
13990         tm = time((time_t *) NULL);
13991
13992         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13993         PrintOpponents(f);
13994         fprintf(f, "[--------------\n");
13995         PrintPosition(f, currentMove);
13996         fprintf(f, "--------------]\n");
13997     } else {
13998         fen = PositionToFEN(currentMove, NULL, 1);
13999         fprintf(f, "%s\n", fen);
14000         free(fen);
14001     }
14002     fclose(f);
14003     return TRUE;
14004 }
14005
14006 void
14007 ReloadCmailMsgEvent (int unregister)
14008 {
14009 #if !WIN32
14010     static char *inFilename = NULL;
14011     static char *outFilename;
14012     int i;
14013     struct stat inbuf, outbuf;
14014     int status;
14015
14016     /* Any registered moves are unregistered if unregister is set, */
14017     /* i.e. invoked by the signal handler */
14018     if (unregister) {
14019         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14020             cmailMoveRegistered[i] = FALSE;
14021             if (cmailCommentList[i] != NULL) {
14022                 free(cmailCommentList[i]);
14023                 cmailCommentList[i] = NULL;
14024             }
14025         }
14026         nCmailMovesRegistered = 0;
14027     }
14028
14029     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14030         cmailResult[i] = CMAIL_NOT_RESULT;
14031     }
14032     nCmailResults = 0;
14033
14034     if (inFilename == NULL) {
14035         /* Because the filenames are static they only get malloced once  */
14036         /* and they never get freed                                      */
14037         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14038         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14039
14040         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14041         sprintf(outFilename, "%s.out", appData.cmailGameName);
14042     }
14043
14044     status = stat(outFilename, &outbuf);
14045     if (status < 0) {
14046         cmailMailedMove = FALSE;
14047     } else {
14048         status = stat(inFilename, &inbuf);
14049         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14050     }
14051
14052     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14053        counts the games, notes how each one terminated, etc.
14054
14055        It would be nice to remove this kludge and instead gather all
14056        the information while building the game list.  (And to keep it
14057        in the game list nodes instead of having a bunch of fixed-size
14058        parallel arrays.)  Note this will require getting each game's
14059        termination from the PGN tags, as the game list builder does
14060        not process the game moves.  --mann
14061        */
14062     cmailMsgLoaded = TRUE;
14063     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14064
14065     /* Load first game in the file or popup game menu */
14066     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14067
14068 #endif /* !WIN32 */
14069     return;
14070 }
14071
14072 int
14073 RegisterMove ()
14074 {
14075     FILE *f;
14076     char string[MSG_SIZ];
14077
14078     if (   cmailMailedMove
14079         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14080         return TRUE;            /* Allow free viewing  */
14081     }
14082
14083     /* Unregister move to ensure that we don't leave RegisterMove        */
14084     /* with the move registered when the conditions for registering no   */
14085     /* longer hold                                                       */
14086     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14087         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14088         nCmailMovesRegistered --;
14089
14090         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14091           {
14092               free(cmailCommentList[lastLoadGameNumber - 1]);
14093               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14094           }
14095     }
14096
14097     if (cmailOldMove == -1) {
14098         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14099         return FALSE;
14100     }
14101
14102     if (currentMove > cmailOldMove + 1) {
14103         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14104         return FALSE;
14105     }
14106
14107     if (currentMove < cmailOldMove) {
14108         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14109         return FALSE;
14110     }
14111
14112     if (forwardMostMove > currentMove) {
14113         /* Silently truncate extra moves */
14114         TruncateGame();
14115     }
14116
14117     if (   (currentMove == cmailOldMove + 1)
14118         || (   (currentMove == cmailOldMove)
14119             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14120                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14121         if (gameInfo.result != GameUnfinished) {
14122             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14123         }
14124
14125         if (commentList[currentMove] != NULL) {
14126             cmailCommentList[lastLoadGameNumber - 1]
14127               = StrSave(commentList[currentMove]);
14128         }
14129         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14130
14131         if (appData.debugMode)
14132           fprintf(debugFP, "Saving %s for game %d\n",
14133                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14134
14135         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14136
14137         f = fopen(string, "w");
14138         if (appData.oldSaveStyle) {
14139             SaveGameOldStyle(f); /* also closes the file */
14140
14141             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14142             f = fopen(string, "w");
14143             SavePosition(f, 0, NULL); /* also closes the file */
14144         } else {
14145             fprintf(f, "{--------------\n");
14146             PrintPosition(f, currentMove);
14147             fprintf(f, "--------------}\n\n");
14148
14149             SaveGame(f, 0, NULL); /* also closes the file*/
14150         }
14151
14152         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14153         nCmailMovesRegistered ++;
14154     } else if (nCmailGames == 1) {
14155         DisplayError(_("You have not made a move yet"), 0);
14156         return FALSE;
14157     }
14158
14159     return TRUE;
14160 }
14161
14162 void
14163 MailMoveEvent ()
14164 {
14165 #if !WIN32
14166     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14167     FILE *commandOutput;
14168     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14169     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14170     int nBuffers;
14171     int i;
14172     int archived;
14173     char *arcDir;
14174
14175     if (! cmailMsgLoaded) {
14176         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14177         return;
14178     }
14179
14180     if (nCmailGames == nCmailResults) {
14181         DisplayError(_("No unfinished games"), 0);
14182         return;
14183     }
14184
14185 #if CMAIL_PROHIBIT_REMAIL
14186     if (cmailMailedMove) {
14187       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);
14188         DisplayError(msg, 0);
14189         return;
14190     }
14191 #endif
14192
14193     if (! (cmailMailedMove || RegisterMove())) return;
14194
14195     if (   cmailMailedMove
14196         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14197       snprintf(string, MSG_SIZ, partCommandString,
14198                appData.debugMode ? " -v" : "", appData.cmailGameName);
14199         commandOutput = popen(string, "r");
14200
14201         if (commandOutput == NULL) {
14202             DisplayError(_("Failed to invoke cmail"), 0);
14203         } else {
14204             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14205                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14206             }
14207             if (nBuffers > 1) {
14208                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14209                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14210                 nBytes = MSG_SIZ - 1;
14211             } else {
14212                 (void) memcpy(msg, buffer, nBytes);
14213             }
14214             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14215
14216             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14217                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14218
14219                 archived = TRUE;
14220                 for (i = 0; i < nCmailGames; i ++) {
14221                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14222                         archived = FALSE;
14223                     }
14224                 }
14225                 if (   archived
14226                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14227                         != NULL)) {
14228                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14229                            arcDir,
14230                            appData.cmailGameName,
14231                            gameInfo.date);
14232                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14233                     cmailMsgLoaded = FALSE;
14234                 }
14235             }
14236
14237             DisplayInformation(msg);
14238             pclose(commandOutput);
14239         }
14240     } else {
14241         if ((*cmailMsg) != '\0') {
14242             DisplayInformation(cmailMsg);
14243         }
14244     }
14245
14246     return;
14247 #endif /* !WIN32 */
14248 }
14249
14250 char *
14251 CmailMsg ()
14252 {
14253 #if WIN32
14254     return NULL;
14255 #else
14256     int  prependComma = 0;
14257     char number[5];
14258     char string[MSG_SIZ];       /* Space for game-list */
14259     int  i;
14260
14261     if (!cmailMsgLoaded) return "";
14262
14263     if (cmailMailedMove) {
14264       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14265     } else {
14266         /* Create a list of games left */
14267       snprintf(string, MSG_SIZ, "[");
14268         for (i = 0; i < nCmailGames; i ++) {
14269             if (! (   cmailMoveRegistered[i]
14270                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14271                 if (prependComma) {
14272                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14273                 } else {
14274                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14275                     prependComma = 1;
14276                 }
14277
14278                 strcat(string, number);
14279             }
14280         }
14281         strcat(string, "]");
14282
14283         if (nCmailMovesRegistered + nCmailResults == 0) {
14284             switch (nCmailGames) {
14285               case 1:
14286                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14287                 break;
14288
14289               case 2:
14290                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14291                 break;
14292
14293               default:
14294                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14295                          nCmailGames);
14296                 break;
14297             }
14298         } else {
14299             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14300               case 1:
14301                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14302                          string);
14303                 break;
14304
14305               case 0:
14306                 if (nCmailResults == nCmailGames) {
14307                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14308                 } else {
14309                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14310                 }
14311                 break;
14312
14313               default:
14314                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14315                          string);
14316             }
14317         }
14318     }
14319     return cmailMsg;
14320 #endif /* WIN32 */
14321 }
14322
14323 void
14324 ResetGameEvent ()
14325 {
14326     if (gameMode == Training)
14327       SetTrainingModeOff();
14328
14329     Reset(TRUE, TRUE);
14330     cmailMsgLoaded = FALSE;
14331     if (appData.icsActive) {
14332       SendToICS(ics_prefix);
14333       SendToICS("refresh\n");
14334     }
14335 }
14336
14337 void
14338 ExitEvent (int status)
14339 {
14340     exiting++;
14341     if (exiting > 2) {
14342       /* Give up on clean exit */
14343       exit(status);
14344     }
14345     if (exiting > 1) {
14346       /* Keep trying for clean exit */
14347       return;
14348     }
14349
14350     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14351     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14352
14353     if (telnetISR != NULL) {
14354       RemoveInputSource(telnetISR);
14355     }
14356     if (icsPR != NoProc) {
14357       DestroyChildProcess(icsPR, TRUE);
14358     }
14359
14360     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14361     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14362
14363     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14364     /* make sure this other one finishes before killing it!                  */
14365     if(endingGame) { int count = 0;
14366         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14367         while(endingGame && count++ < 10) DoSleep(1);
14368         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14369     }
14370
14371     /* Kill off chess programs */
14372     if (first.pr != NoProc) {
14373         ExitAnalyzeMode();
14374
14375         DoSleep( appData.delayBeforeQuit );
14376         SendToProgram("quit\n", &first);
14377         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14378     }
14379     if (second.pr != NoProc) {
14380         DoSleep( appData.delayBeforeQuit );
14381         SendToProgram("quit\n", &second);
14382         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14383     }
14384     if (first.isr != NULL) {
14385         RemoveInputSource(first.isr);
14386     }
14387     if (second.isr != NULL) {
14388         RemoveInputSource(second.isr);
14389     }
14390
14391     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14392     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14393
14394     ShutDownFrontEnd();
14395     exit(status);
14396 }
14397
14398 void
14399 PauseEngine (ChessProgramState *cps)
14400 {
14401     SendToProgram("pause\n", cps);
14402     cps->pause = 2;
14403 }
14404
14405 void
14406 UnPauseEngine (ChessProgramState *cps)
14407 {
14408     SendToProgram("resume\n", cps);
14409     cps->pause = 1;
14410 }
14411
14412 void
14413 PauseEvent ()
14414 {
14415     if (appData.debugMode)
14416         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14417     if (pausing) {
14418         pausing = FALSE;
14419         ModeHighlight();
14420         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14421             StartClocks();
14422             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14423                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14424                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14425             }
14426             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14427             HandleMachineMove(stashedInputMove, stalledEngine);
14428             stalledEngine = NULL;
14429             return;
14430         }
14431         if (gameMode == MachinePlaysWhite ||
14432             gameMode == TwoMachinesPlay   ||
14433             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14434             if(first.pause)  UnPauseEngine(&first);
14435             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14436             if(second.pause) UnPauseEngine(&second);
14437             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14438             StartClocks();
14439         } else {
14440             DisplayBothClocks();
14441         }
14442         if (gameMode == PlayFromGameFile) {
14443             if (appData.timeDelay >= 0)
14444                 AutoPlayGameLoop();
14445         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14446             Reset(FALSE, TRUE);
14447             SendToICS(ics_prefix);
14448             SendToICS("refresh\n");
14449         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14450             ForwardInner(forwardMostMove);
14451         }
14452         pauseExamInvalid = FALSE;
14453     } else {
14454         switch (gameMode) {
14455           default:
14456             return;
14457           case IcsExamining:
14458             pauseExamForwardMostMove = forwardMostMove;
14459             pauseExamInvalid = FALSE;
14460             /* fall through */
14461           case IcsObserving:
14462           case IcsPlayingWhite:
14463           case IcsPlayingBlack:
14464             pausing = TRUE;
14465             ModeHighlight();
14466             return;
14467           case PlayFromGameFile:
14468             (void) StopLoadGameTimer();
14469             pausing = TRUE;
14470             ModeHighlight();
14471             break;
14472           case BeginningOfGame:
14473             if (appData.icsActive) return;
14474             /* else fall through */
14475           case MachinePlaysWhite:
14476           case MachinePlaysBlack:
14477           case TwoMachinesPlay:
14478             if (forwardMostMove == 0)
14479               return;           /* don't pause if no one has moved */
14480             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14481                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14482                 if(onMove->pause) {           // thinking engine can be paused
14483                     PauseEngine(onMove);      // do it
14484                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14485                         PauseEngine(onMove->other);
14486                     else
14487                         SendToProgram("easy\n", onMove->other);
14488                     StopClocks();
14489                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14490             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14491                 if(first.pause) {
14492                     PauseEngine(&first);
14493                     StopClocks();
14494                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14495             } else { // human on move, pause pondering by either method
14496                 if(first.pause)
14497                     PauseEngine(&first);
14498                 else if(appData.ponderNextMove)
14499                     SendToProgram("easy\n", &first);
14500                 StopClocks();
14501             }
14502             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14503           case AnalyzeMode:
14504             pausing = TRUE;
14505             ModeHighlight();
14506             break;
14507         }
14508     }
14509 }
14510
14511 void
14512 EditCommentEvent ()
14513 {
14514     char title[MSG_SIZ];
14515
14516     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14517       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14518     } else {
14519       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14520                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14521                parseList[currentMove - 1]);
14522     }
14523
14524     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14525 }
14526
14527
14528 void
14529 EditTagsEvent ()
14530 {
14531     char *tags = PGNTags(&gameInfo);
14532     bookUp = FALSE;
14533     EditTagsPopUp(tags, NULL);
14534     free(tags);
14535 }
14536
14537 void
14538 ToggleSecond ()
14539 {
14540   if(second.analyzing) {
14541     SendToProgram("exit\n", &second);
14542     second.analyzing = FALSE;
14543   } else {
14544     if (second.pr == NoProc) StartChessProgram(&second);
14545     InitChessProgram(&second, FALSE);
14546     FeedMovesToProgram(&second, currentMove);
14547
14548     SendToProgram("analyze\n", &second);
14549     second.analyzing = TRUE;
14550   }
14551 }
14552
14553 /* Toggle ShowThinking */
14554 void
14555 ToggleShowThinking()
14556 {
14557   appData.showThinking = !appData.showThinking;
14558   ShowThinkingEvent();
14559 }
14560
14561 int
14562 AnalyzeModeEvent ()
14563 {
14564     char buf[MSG_SIZ];
14565
14566     if (!first.analysisSupport) {
14567       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14568       DisplayError(buf, 0);
14569       return 0;
14570     }
14571     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14572     if (appData.icsActive) {
14573         if (gameMode != IcsObserving) {
14574           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14575             DisplayError(buf, 0);
14576             /* secure check */
14577             if (appData.icsEngineAnalyze) {
14578                 if (appData.debugMode)
14579                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14580                 ExitAnalyzeMode();
14581                 ModeHighlight();
14582             }
14583             return 0;
14584         }
14585         /* if enable, user wants to disable icsEngineAnalyze */
14586         if (appData.icsEngineAnalyze) {
14587                 ExitAnalyzeMode();
14588                 ModeHighlight();
14589                 return 0;
14590         }
14591         appData.icsEngineAnalyze = TRUE;
14592         if (appData.debugMode)
14593             fprintf(debugFP, "ICS engine analyze starting... \n");
14594     }
14595
14596     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14597     if (appData.noChessProgram || gameMode == AnalyzeMode)
14598       return 0;
14599
14600     if (gameMode != AnalyzeFile) {
14601         if (!appData.icsEngineAnalyze) {
14602                EditGameEvent();
14603                if (gameMode != EditGame) return 0;
14604         }
14605         if (!appData.showThinking) ToggleShowThinking();
14606         ResurrectChessProgram();
14607         SendToProgram("analyze\n", &first);
14608         first.analyzing = TRUE;
14609         /*first.maybeThinking = TRUE;*/
14610         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14611         EngineOutputPopUp();
14612     }
14613     if (!appData.icsEngineAnalyze) {
14614         gameMode = AnalyzeMode;
14615         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14616     }
14617     pausing = FALSE;
14618     ModeHighlight();
14619     SetGameInfo();
14620
14621     StartAnalysisClock();
14622     GetTimeMark(&lastNodeCountTime);
14623     lastNodeCount = 0;
14624     return 1;
14625 }
14626
14627 void
14628 AnalyzeFileEvent ()
14629 {
14630     if (appData.noChessProgram || gameMode == AnalyzeFile)
14631       return;
14632
14633     if (!first.analysisSupport) {
14634       char buf[MSG_SIZ];
14635       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14636       DisplayError(buf, 0);
14637       return;
14638     }
14639
14640     if (gameMode != AnalyzeMode) {
14641         keepInfo = 1; // mere annotating should not alter PGN tags
14642         EditGameEvent();
14643         keepInfo = 0;
14644         if (gameMode != EditGame) return;
14645         if (!appData.showThinking) ToggleShowThinking();
14646         ResurrectChessProgram();
14647         SendToProgram("analyze\n", &first);
14648         first.analyzing = TRUE;
14649         /*first.maybeThinking = TRUE;*/
14650         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14651         EngineOutputPopUp();
14652     }
14653     gameMode = AnalyzeFile;
14654     pausing = FALSE;
14655     ModeHighlight();
14656
14657     StartAnalysisClock();
14658     GetTimeMark(&lastNodeCountTime);
14659     lastNodeCount = 0;
14660     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14661     AnalysisPeriodicEvent(1);
14662 }
14663
14664 void
14665 MachineWhiteEvent ()
14666 {
14667     char buf[MSG_SIZ];
14668     char *bookHit = NULL;
14669
14670     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14671       return;
14672
14673
14674     if (gameMode == PlayFromGameFile ||
14675         gameMode == TwoMachinesPlay  ||
14676         gameMode == Training         ||
14677         gameMode == AnalyzeMode      ||
14678         gameMode == EndOfGame)
14679         EditGameEvent();
14680
14681     if (gameMode == EditPosition)
14682         EditPositionDone(TRUE);
14683
14684     if (!WhiteOnMove(currentMove)) {
14685         DisplayError(_("It is not White's turn"), 0);
14686         return;
14687     }
14688
14689     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14690       ExitAnalyzeMode();
14691
14692     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14693         gameMode == AnalyzeFile)
14694         TruncateGame();
14695
14696     ResurrectChessProgram();    /* in case it isn't running */
14697     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14698         gameMode = MachinePlaysWhite;
14699         ResetClocks();
14700     } else
14701     gameMode = MachinePlaysWhite;
14702     pausing = FALSE;
14703     ModeHighlight();
14704     SetGameInfo();
14705     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14706     DisplayTitle(buf);
14707     if (first.sendName) {
14708       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14709       SendToProgram(buf, &first);
14710     }
14711     if (first.sendTime) {
14712       if (first.useColors) {
14713         SendToProgram("black\n", &first); /*gnu kludge*/
14714       }
14715       SendTimeRemaining(&first, TRUE);
14716     }
14717     if (first.useColors) {
14718       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14719     }
14720     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14721     SetMachineThinkingEnables();
14722     first.maybeThinking = TRUE;
14723     StartClocks();
14724     firstMove = FALSE;
14725
14726     if (appData.autoFlipView && !flipView) {
14727       flipView = !flipView;
14728       DrawPosition(FALSE, NULL);
14729       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14730     }
14731
14732     if(bookHit) { // [HGM] book: simulate book reply
14733         static char bookMove[MSG_SIZ]; // a bit generous?
14734
14735         programStats.nodes = programStats.depth = programStats.time =
14736         programStats.score = programStats.got_only_move = 0;
14737         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14738
14739         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14740         strcat(bookMove, bookHit);
14741         HandleMachineMove(bookMove, &first);
14742     }
14743 }
14744
14745 void
14746 MachineBlackEvent ()
14747 {
14748   char buf[MSG_SIZ];
14749   char *bookHit = NULL;
14750
14751     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14752         return;
14753
14754
14755     if (gameMode == PlayFromGameFile ||
14756         gameMode == TwoMachinesPlay  ||
14757         gameMode == Training         ||
14758         gameMode == AnalyzeMode      ||
14759         gameMode == EndOfGame)
14760         EditGameEvent();
14761
14762     if (gameMode == EditPosition)
14763         EditPositionDone(TRUE);
14764
14765     if (WhiteOnMove(currentMove)) {
14766         DisplayError(_("It is not Black's turn"), 0);
14767         return;
14768     }
14769
14770     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14771       ExitAnalyzeMode();
14772
14773     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14774         gameMode == AnalyzeFile)
14775         TruncateGame();
14776
14777     ResurrectChessProgram();    /* in case it isn't running */
14778     gameMode = MachinePlaysBlack;
14779     pausing = FALSE;
14780     ModeHighlight();
14781     SetGameInfo();
14782     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14783     DisplayTitle(buf);
14784     if (first.sendName) {
14785       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14786       SendToProgram(buf, &first);
14787     }
14788     if (first.sendTime) {
14789       if (first.useColors) {
14790         SendToProgram("white\n", &first); /*gnu kludge*/
14791       }
14792       SendTimeRemaining(&first, FALSE);
14793     }
14794     if (first.useColors) {
14795       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14796     }
14797     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14798     SetMachineThinkingEnables();
14799     first.maybeThinking = TRUE;
14800     StartClocks();
14801
14802     if (appData.autoFlipView && flipView) {
14803       flipView = !flipView;
14804       DrawPosition(FALSE, NULL);
14805       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14806     }
14807     if(bookHit) { // [HGM] book: simulate book reply
14808         static char bookMove[MSG_SIZ]; // a bit generous?
14809
14810         programStats.nodes = programStats.depth = programStats.time =
14811         programStats.score = programStats.got_only_move = 0;
14812         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14813
14814         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14815         strcat(bookMove, bookHit);
14816         HandleMachineMove(bookMove, &first);
14817     }
14818 }
14819
14820
14821 void
14822 DisplayTwoMachinesTitle ()
14823 {
14824     char buf[MSG_SIZ];
14825     if (appData.matchGames > 0) {
14826         if(appData.tourneyFile[0]) {
14827           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14828                    gameInfo.white, _("vs."), gameInfo.black,
14829                    nextGame+1, appData.matchGames+1,
14830                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14831         } else
14832         if (first.twoMachinesColor[0] == 'w') {
14833           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14834                    gameInfo.white, _("vs."),  gameInfo.black,
14835                    first.matchWins, second.matchWins,
14836                    matchGame - 1 - (first.matchWins + second.matchWins));
14837         } else {
14838           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14839                    gameInfo.white, _("vs."), gameInfo.black,
14840                    second.matchWins, first.matchWins,
14841                    matchGame - 1 - (first.matchWins + second.matchWins));
14842         }
14843     } else {
14844       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14845     }
14846     DisplayTitle(buf);
14847 }
14848
14849 void
14850 SettingsMenuIfReady ()
14851 {
14852   if (second.lastPing != second.lastPong) {
14853     DisplayMessage("", _("Waiting for second chess program"));
14854     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14855     return;
14856   }
14857   ThawUI();
14858   DisplayMessage("", "");
14859   SettingsPopUp(&second);
14860 }
14861
14862 int
14863 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14864 {
14865     char buf[MSG_SIZ];
14866     if (cps->pr == NoProc) {
14867         StartChessProgram(cps);
14868         if (cps->protocolVersion == 1) {
14869           retry();
14870           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14871         } else {
14872           /* kludge: allow timeout for initial "feature" command */
14873           if(retry != TwoMachinesEventIfReady) FreezeUI();
14874           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14875           DisplayMessage("", buf);
14876           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14877         }
14878         return 1;
14879     }
14880     return 0;
14881 }
14882
14883 void
14884 TwoMachinesEvent P((void))
14885 {
14886     int i;
14887     char buf[MSG_SIZ];
14888     ChessProgramState *onmove;
14889     char *bookHit = NULL;
14890     static int stalling = 0;
14891     TimeMark now;
14892     long wait;
14893
14894     if (appData.noChessProgram) return;
14895
14896     switch (gameMode) {
14897       case TwoMachinesPlay:
14898         return;
14899       case MachinePlaysWhite:
14900       case MachinePlaysBlack:
14901         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14902             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14903             return;
14904         }
14905         /* fall through */
14906       case BeginningOfGame:
14907       case PlayFromGameFile:
14908       case EndOfGame:
14909         EditGameEvent();
14910         if (gameMode != EditGame) return;
14911         break;
14912       case EditPosition:
14913         EditPositionDone(TRUE);
14914         break;
14915       case AnalyzeMode:
14916       case AnalyzeFile:
14917         ExitAnalyzeMode();
14918         break;
14919       case EditGame:
14920       default:
14921         break;
14922     }
14923
14924 //    forwardMostMove = currentMove;
14925     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14926     startingEngine = TRUE;
14927
14928     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14929
14930     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14931     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14932       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14933       return;
14934     }
14935     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14936
14937     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14938                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14939         startingEngine = matchMode = FALSE;
14940         DisplayError("second engine does not play this", 0);
14941         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14942         EditGameEvent(); // switch back to EditGame mode
14943         return;
14944     }
14945
14946     if(!stalling) {
14947       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14948       SendToProgram("force\n", &second);
14949       stalling = 1;
14950       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14951       return;
14952     }
14953     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14954     if(appData.matchPause>10000 || appData.matchPause<10)
14955                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14956     wait = SubtractTimeMarks(&now, &pauseStart);
14957     if(wait < appData.matchPause) {
14958         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14959         return;
14960     }
14961     // we are now committed to starting the game
14962     stalling = 0;
14963     DisplayMessage("", "");
14964     if (startedFromSetupPosition) {
14965         SendBoard(&second, backwardMostMove);
14966     if (appData.debugMode) {
14967         fprintf(debugFP, "Two Machines\n");
14968     }
14969     }
14970     for (i = backwardMostMove; i < forwardMostMove; i++) {
14971         SendMoveToProgram(i, &second);
14972     }
14973
14974     gameMode = TwoMachinesPlay;
14975     pausing = startingEngine = FALSE;
14976     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14977     SetGameInfo();
14978     DisplayTwoMachinesTitle();
14979     firstMove = TRUE;
14980     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14981         onmove = &first;
14982     } else {
14983         onmove = &second;
14984     }
14985     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14986     SendToProgram(first.computerString, &first);
14987     if (first.sendName) {
14988       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14989       SendToProgram(buf, &first);
14990     }
14991     SendToProgram(second.computerString, &second);
14992     if (second.sendName) {
14993       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14994       SendToProgram(buf, &second);
14995     }
14996
14997     ResetClocks();
14998     if (!first.sendTime || !second.sendTime) {
14999         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15000         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15001     }
15002     if (onmove->sendTime) {
15003       if (onmove->useColors) {
15004         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15005       }
15006       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15007     }
15008     if (onmove->useColors) {
15009       SendToProgram(onmove->twoMachinesColor, onmove);
15010     }
15011     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15012 //    SendToProgram("go\n", onmove);
15013     onmove->maybeThinking = TRUE;
15014     SetMachineThinkingEnables();
15015
15016     StartClocks();
15017
15018     if(bookHit) { // [HGM] book: simulate book reply
15019         static char bookMove[MSG_SIZ]; // a bit generous?
15020
15021         programStats.nodes = programStats.depth = programStats.time =
15022         programStats.score = programStats.got_only_move = 0;
15023         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15024
15025         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15026         strcat(bookMove, bookHit);
15027         savedMessage = bookMove; // args for deferred call
15028         savedState = onmove;
15029         ScheduleDelayedEvent(DeferredBookMove, 1);
15030     }
15031 }
15032
15033 void
15034 TrainingEvent ()
15035 {
15036     if (gameMode == Training) {
15037       SetTrainingModeOff();
15038       gameMode = PlayFromGameFile;
15039       DisplayMessage("", _("Training mode off"));
15040     } else {
15041       gameMode = Training;
15042       animateTraining = appData.animate;
15043
15044       /* make sure we are not already at the end of the game */
15045       if (currentMove < forwardMostMove) {
15046         SetTrainingModeOn();
15047         DisplayMessage("", _("Training mode on"));
15048       } else {
15049         gameMode = PlayFromGameFile;
15050         DisplayError(_("Already at end of game"), 0);
15051       }
15052     }
15053     ModeHighlight();
15054 }
15055
15056 void
15057 IcsClientEvent ()
15058 {
15059     if (!appData.icsActive) return;
15060     switch (gameMode) {
15061       case IcsPlayingWhite:
15062       case IcsPlayingBlack:
15063       case IcsObserving:
15064       case IcsIdle:
15065       case BeginningOfGame:
15066       case IcsExamining:
15067         return;
15068
15069       case EditGame:
15070         break;
15071
15072       case EditPosition:
15073         EditPositionDone(TRUE);
15074         break;
15075
15076       case AnalyzeMode:
15077       case AnalyzeFile:
15078         ExitAnalyzeMode();
15079         break;
15080
15081       default:
15082         EditGameEvent();
15083         break;
15084     }
15085
15086     gameMode = IcsIdle;
15087     ModeHighlight();
15088     return;
15089 }
15090
15091 void
15092 EditGameEvent ()
15093 {
15094     int i;
15095
15096     switch (gameMode) {
15097       case Training:
15098         SetTrainingModeOff();
15099         break;
15100       case MachinePlaysWhite:
15101       case MachinePlaysBlack:
15102       case BeginningOfGame:
15103         SendToProgram("force\n", &first);
15104         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15105             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15106                 char buf[MSG_SIZ];
15107                 abortEngineThink = TRUE;
15108                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15109                 SendToProgram(buf, &first);
15110                 DisplayMessage("Aborting engine think", "");
15111                 FreezeUI();
15112             }
15113         }
15114         SetUserThinkingEnables();
15115         break;
15116       case PlayFromGameFile:
15117         (void) StopLoadGameTimer();
15118         if (gameFileFP != NULL) {
15119             gameFileFP = NULL;
15120         }
15121         break;
15122       case EditPosition:
15123         EditPositionDone(TRUE);
15124         break;
15125       case AnalyzeMode:
15126       case AnalyzeFile:
15127         ExitAnalyzeMode();
15128         SendToProgram("force\n", &first);
15129         break;
15130       case TwoMachinesPlay:
15131         GameEnds(EndOfFile, NULL, GE_PLAYER);
15132         ResurrectChessProgram();
15133         SetUserThinkingEnables();
15134         break;
15135       case EndOfGame:
15136         ResurrectChessProgram();
15137         break;
15138       case IcsPlayingBlack:
15139       case IcsPlayingWhite:
15140         DisplayError(_("Warning: You are still playing a game"), 0);
15141         break;
15142       case IcsObserving:
15143         DisplayError(_("Warning: You are still observing a game"), 0);
15144         break;
15145       case IcsExamining:
15146         DisplayError(_("Warning: You are still examining a game"), 0);
15147         break;
15148       case IcsIdle:
15149         break;
15150       case EditGame:
15151       default:
15152         return;
15153     }
15154
15155     pausing = FALSE;
15156     StopClocks();
15157     first.offeredDraw = second.offeredDraw = 0;
15158
15159     if (gameMode == PlayFromGameFile) {
15160         whiteTimeRemaining = timeRemaining[0][currentMove];
15161         blackTimeRemaining = timeRemaining[1][currentMove];
15162         DisplayTitle("");
15163     }
15164
15165     if (gameMode == MachinePlaysWhite ||
15166         gameMode == MachinePlaysBlack ||
15167         gameMode == TwoMachinesPlay ||
15168         gameMode == EndOfGame) {
15169         i = forwardMostMove;
15170         while (i > currentMove) {
15171             SendToProgram("undo\n", &first);
15172             i--;
15173         }
15174         if(!adjustedClock) {
15175         whiteTimeRemaining = timeRemaining[0][currentMove];
15176         blackTimeRemaining = timeRemaining[1][currentMove];
15177         DisplayBothClocks();
15178         }
15179         if (whiteFlag || blackFlag) {
15180             whiteFlag = blackFlag = 0;
15181         }
15182         DisplayTitle("");
15183     }
15184
15185     gameMode = EditGame;
15186     ModeHighlight();
15187     SetGameInfo();
15188 }
15189
15190
15191 void
15192 EditPositionEvent ()
15193 {
15194     if (gameMode == EditPosition) {
15195         EditGameEvent();
15196         return;
15197     }
15198
15199     EditGameEvent();
15200     if (gameMode != EditGame) return;
15201
15202     gameMode = EditPosition;
15203     ModeHighlight();
15204     SetGameInfo();
15205     if (currentMove > 0)
15206       CopyBoard(boards[0], boards[currentMove]);
15207
15208     blackPlaysFirst = !WhiteOnMove(currentMove);
15209     ResetClocks();
15210     currentMove = forwardMostMove = backwardMostMove = 0;
15211     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15212     DisplayMove(-1);
15213     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15214 }
15215
15216 void
15217 ExitAnalyzeMode ()
15218 {
15219     /* [DM] icsEngineAnalyze - possible call from other functions */
15220     if (appData.icsEngineAnalyze) {
15221         appData.icsEngineAnalyze = FALSE;
15222
15223         DisplayMessage("",_("Close ICS engine analyze..."));
15224     }
15225     if (first.analysisSupport && first.analyzing) {
15226       SendToBoth("exit\n");
15227       first.analyzing = second.analyzing = FALSE;
15228     }
15229     thinkOutput[0] = NULLCHAR;
15230 }
15231
15232 void
15233 EditPositionDone (Boolean fakeRights)
15234 {
15235     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15236
15237     startedFromSetupPosition = TRUE;
15238     InitChessProgram(&first, FALSE);
15239     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15240       boards[0][EP_STATUS] = EP_NONE;
15241       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15242       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15243         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15244         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15245       } else boards[0][CASTLING][2] = NoRights;
15246       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15247         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15248         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15249       } else boards[0][CASTLING][5] = NoRights;
15250       if(gameInfo.variant == VariantSChess) {
15251         int i;
15252         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15253           boards[0][VIRGIN][i] = 0;
15254           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15255           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15256         }
15257       }
15258     }
15259     SendToProgram("force\n", &first);
15260     if (blackPlaysFirst) {
15261         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15262         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15263         currentMove = forwardMostMove = backwardMostMove = 1;
15264         CopyBoard(boards[1], boards[0]);
15265     } else {
15266         currentMove = forwardMostMove = backwardMostMove = 0;
15267     }
15268     SendBoard(&first, forwardMostMove);
15269     if (appData.debugMode) {
15270         fprintf(debugFP, "EditPosDone\n");
15271     }
15272     DisplayTitle("");
15273     DisplayMessage("", "");
15274     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15275     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15276     gameMode = EditGame;
15277     ModeHighlight();
15278     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15279     ClearHighlights(); /* [AS] */
15280 }
15281
15282 /* Pause for `ms' milliseconds */
15283 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15284 void
15285 TimeDelay (long ms)
15286 {
15287     TimeMark m1, m2;
15288
15289     GetTimeMark(&m1);
15290     do {
15291         GetTimeMark(&m2);
15292     } while (SubtractTimeMarks(&m2, &m1) < ms);
15293 }
15294
15295 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15296 void
15297 SendMultiLineToICS (char *buf)
15298 {
15299     char temp[MSG_SIZ+1], *p;
15300     int len;
15301
15302     len = strlen(buf);
15303     if (len > MSG_SIZ)
15304       len = MSG_SIZ;
15305
15306     strncpy(temp, buf, len);
15307     temp[len] = 0;
15308
15309     p = temp;
15310     while (*p) {
15311         if (*p == '\n' || *p == '\r')
15312           *p = ' ';
15313         ++p;
15314     }
15315
15316     strcat(temp, "\n");
15317     SendToICS(temp);
15318     SendToPlayer(temp, strlen(temp));
15319 }
15320
15321 void
15322 SetWhiteToPlayEvent ()
15323 {
15324     if (gameMode == EditPosition) {
15325         blackPlaysFirst = FALSE;
15326         DisplayBothClocks();    /* works because currentMove is 0 */
15327     } else if (gameMode == IcsExamining) {
15328         SendToICS(ics_prefix);
15329         SendToICS("tomove white\n");
15330     }
15331 }
15332
15333 void
15334 SetBlackToPlayEvent ()
15335 {
15336     if (gameMode == EditPosition) {
15337         blackPlaysFirst = TRUE;
15338         currentMove = 1;        /* kludge */
15339         DisplayBothClocks();
15340         currentMove = 0;
15341     } else if (gameMode == IcsExamining) {
15342         SendToICS(ics_prefix);
15343         SendToICS("tomove black\n");
15344     }
15345 }
15346
15347 void
15348 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15349 {
15350     char buf[MSG_SIZ];
15351     ChessSquare piece = boards[0][y][x];
15352     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15353     static int lastVariant;
15354
15355     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15356
15357     switch (selection) {
15358       case ClearBoard:
15359         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15360         MarkTargetSquares(1);
15361         CopyBoard(currentBoard, boards[0]);
15362         CopyBoard(menuBoard, initialPosition);
15363         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15364             SendToICS(ics_prefix);
15365             SendToICS("bsetup clear\n");
15366         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15367             SendToICS(ics_prefix);
15368             SendToICS("clearboard\n");
15369         } else {
15370             int nonEmpty = 0;
15371             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15372                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15373                 for (y = 0; y < BOARD_HEIGHT; y++) {
15374                     if (gameMode == IcsExamining) {
15375                         if (boards[currentMove][y][x] != EmptySquare) {
15376                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15377                                     AAA + x, ONE + y);
15378                             SendToICS(buf);
15379                         }
15380                     } else if(boards[0][y][x] != DarkSquare) {
15381                         if(boards[0][y][x] != p) nonEmpty++;
15382                         boards[0][y][x] = p;
15383                     }
15384                 }
15385             }
15386             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15387                 int r;
15388                 for(r = 0; r < BOARD_HEIGHT; r++) {
15389                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15390                     ChessSquare p = menuBoard[r][x];
15391                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15392                   }
15393                 }
15394                 DisplayMessage("Clicking clock again restores position", "");
15395                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15396                 if(!nonEmpty) { // asked to clear an empty board
15397                     CopyBoard(boards[0], menuBoard);
15398                 } else
15399                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15400                     CopyBoard(boards[0], initialPosition);
15401                 } else
15402                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15403                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15404                     CopyBoard(boards[0], erasedBoard);
15405                 } else
15406                     CopyBoard(erasedBoard, currentBoard);
15407
15408             }
15409         }
15410         if (gameMode == EditPosition) {
15411             DrawPosition(FALSE, boards[0]);
15412         }
15413         break;
15414
15415       case WhitePlay:
15416         SetWhiteToPlayEvent();
15417         break;
15418
15419       case BlackPlay:
15420         SetBlackToPlayEvent();
15421         break;
15422
15423       case EmptySquare:
15424         if (gameMode == IcsExamining) {
15425             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15426             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15427             SendToICS(buf);
15428         } else {
15429             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15430                 if(x == BOARD_LEFT-2) {
15431                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15432                     boards[0][y][1] = 0;
15433                 } else
15434                 if(x == BOARD_RGHT+1) {
15435                     if(y >= gameInfo.holdingsSize) break;
15436                     boards[0][y][BOARD_WIDTH-2] = 0;
15437                 } else break;
15438             }
15439             boards[0][y][x] = EmptySquare;
15440             DrawPosition(FALSE, boards[0]);
15441         }
15442         break;
15443
15444       case PromotePiece:
15445         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15446            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15447             selection = (ChessSquare) (PROMOTED(piece));
15448         } else if(piece == EmptySquare) selection = WhiteSilver;
15449         else selection = (ChessSquare)((int)piece - 1);
15450         goto defaultlabel;
15451
15452       case DemotePiece:
15453         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15454            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15455             selection = (ChessSquare) (DEMOTED(piece));
15456         } else if(piece == EmptySquare) selection = BlackSilver;
15457         else selection = (ChessSquare)((int)piece + 1);
15458         goto defaultlabel;
15459
15460       case WhiteQueen:
15461       case BlackQueen:
15462         if(gameInfo.variant == VariantShatranj ||
15463            gameInfo.variant == VariantXiangqi  ||
15464            gameInfo.variant == VariantCourier  ||
15465            gameInfo.variant == VariantASEAN    ||
15466            gameInfo.variant == VariantMakruk     )
15467             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15468         goto defaultlabel;
15469
15470       case WhiteKing:
15471       case BlackKing:
15472         if(gameInfo.variant == VariantXiangqi)
15473             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15474         if(gameInfo.variant == VariantKnightmate)
15475             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15476       default:
15477         defaultlabel:
15478         if (gameMode == IcsExamining) {
15479             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15480             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15481                      PieceToChar(selection), AAA + x, ONE + y);
15482             SendToICS(buf);
15483         } else {
15484             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15485                 int n;
15486                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15487                     n = PieceToNumber(selection - BlackPawn);
15488                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15489                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15490                     boards[0][BOARD_HEIGHT-1-n][1]++;
15491                 } else
15492                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15493                     n = PieceToNumber(selection);
15494                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15495                     boards[0][n][BOARD_WIDTH-1] = selection;
15496                     boards[0][n][BOARD_WIDTH-2]++;
15497                 }
15498             } else
15499             boards[0][y][x] = selection;
15500             DrawPosition(TRUE, boards[0]);
15501             ClearHighlights();
15502             fromX = fromY = -1;
15503         }
15504         break;
15505     }
15506 }
15507
15508
15509 void
15510 DropMenuEvent (ChessSquare selection, int x, int y)
15511 {
15512     ChessMove moveType;
15513
15514     switch (gameMode) {
15515       case IcsPlayingWhite:
15516       case MachinePlaysBlack:
15517         if (!WhiteOnMove(currentMove)) {
15518             DisplayMoveError(_("It is Black's turn"));
15519             return;
15520         }
15521         moveType = WhiteDrop;
15522         break;
15523       case IcsPlayingBlack:
15524       case MachinePlaysWhite:
15525         if (WhiteOnMove(currentMove)) {
15526             DisplayMoveError(_("It is White's turn"));
15527             return;
15528         }
15529         moveType = BlackDrop;
15530         break;
15531       case EditGame:
15532         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15533         break;
15534       default:
15535         return;
15536     }
15537
15538     if (moveType == BlackDrop && selection < BlackPawn) {
15539       selection = (ChessSquare) ((int) selection
15540                                  + (int) BlackPawn - (int) WhitePawn);
15541     }
15542     if (boards[currentMove][y][x] != EmptySquare) {
15543         DisplayMoveError(_("That square is occupied"));
15544         return;
15545     }
15546
15547     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15548 }
15549
15550 void
15551 AcceptEvent ()
15552 {
15553     /* Accept a pending offer of any kind from opponent */
15554
15555     if (appData.icsActive) {
15556         SendToICS(ics_prefix);
15557         SendToICS("accept\n");
15558     } else if (cmailMsgLoaded) {
15559         if (currentMove == cmailOldMove &&
15560             commentList[cmailOldMove] != NULL &&
15561             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15562                    "Black offers a draw" : "White offers a draw")) {
15563             TruncateGame();
15564             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15565             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15566         } else {
15567             DisplayError(_("There is no pending offer on this move"), 0);
15568             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15569         }
15570     } else {
15571         /* Not used for offers from chess program */
15572     }
15573 }
15574
15575 void
15576 DeclineEvent ()
15577 {
15578     /* Decline a pending offer of any kind from opponent */
15579
15580     if (appData.icsActive) {
15581         SendToICS(ics_prefix);
15582         SendToICS("decline\n");
15583     } else if (cmailMsgLoaded) {
15584         if (currentMove == cmailOldMove &&
15585             commentList[cmailOldMove] != NULL &&
15586             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15587                    "Black offers a draw" : "White offers a draw")) {
15588 #ifdef NOTDEF
15589             AppendComment(cmailOldMove, "Draw declined", TRUE);
15590             DisplayComment(cmailOldMove - 1, "Draw declined");
15591 #endif /*NOTDEF*/
15592         } else {
15593             DisplayError(_("There is no pending offer on this move"), 0);
15594         }
15595     } else {
15596         /* Not used for offers from chess program */
15597     }
15598 }
15599
15600 void
15601 RematchEvent ()
15602 {
15603     /* Issue ICS rematch command */
15604     if (appData.icsActive) {
15605         SendToICS(ics_prefix);
15606         SendToICS("rematch\n");
15607     }
15608 }
15609
15610 void
15611 CallFlagEvent ()
15612 {
15613     /* Call your opponent's flag (claim a win on time) */
15614     if (appData.icsActive) {
15615         SendToICS(ics_prefix);
15616         SendToICS("flag\n");
15617     } else {
15618         switch (gameMode) {
15619           default:
15620             return;
15621           case MachinePlaysWhite:
15622             if (whiteFlag) {
15623                 if (blackFlag)
15624                   GameEnds(GameIsDrawn, "Both players ran out of time",
15625                            GE_PLAYER);
15626                 else
15627                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15628             } else {
15629                 DisplayError(_("Your opponent is not out of time"), 0);
15630             }
15631             break;
15632           case MachinePlaysBlack:
15633             if (blackFlag) {
15634                 if (whiteFlag)
15635                   GameEnds(GameIsDrawn, "Both players ran out of time",
15636                            GE_PLAYER);
15637                 else
15638                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15639             } else {
15640                 DisplayError(_("Your opponent is not out of time"), 0);
15641             }
15642             break;
15643         }
15644     }
15645 }
15646
15647 void
15648 ClockClick (int which)
15649 {       // [HGM] code moved to back-end from winboard.c
15650         if(which) { // black clock
15651           if (gameMode == EditPosition || gameMode == IcsExamining) {
15652             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15653             SetBlackToPlayEvent();
15654           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15655                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15656           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15657           } else if (shiftKey) {
15658             AdjustClock(which, -1);
15659           } else if (gameMode == IcsPlayingWhite ||
15660                      gameMode == MachinePlaysBlack) {
15661             CallFlagEvent();
15662           }
15663         } else { // white clock
15664           if (gameMode == EditPosition || gameMode == IcsExamining) {
15665             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15666             SetWhiteToPlayEvent();
15667           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15668                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15669           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15670           } else if (shiftKey) {
15671             AdjustClock(which, -1);
15672           } else if (gameMode == IcsPlayingBlack ||
15673                    gameMode == MachinePlaysWhite) {
15674             CallFlagEvent();
15675           }
15676         }
15677 }
15678
15679 void
15680 DrawEvent ()
15681 {
15682     /* Offer draw or accept pending draw offer from opponent */
15683
15684     if (appData.icsActive) {
15685         /* Note: tournament rules require draw offers to be
15686            made after you make your move but before you punch
15687            your clock.  Currently ICS doesn't let you do that;
15688            instead, you immediately punch your clock after making
15689            a move, but you can offer a draw at any time. */
15690
15691         SendToICS(ics_prefix);
15692         SendToICS("draw\n");
15693         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15694     } else if (cmailMsgLoaded) {
15695         if (currentMove == cmailOldMove &&
15696             commentList[cmailOldMove] != NULL &&
15697             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15698                    "Black offers a draw" : "White offers a draw")) {
15699             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15700             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15701         } else if (currentMove == cmailOldMove + 1) {
15702             char *offer = WhiteOnMove(cmailOldMove) ?
15703               "White offers a draw" : "Black offers a draw";
15704             AppendComment(currentMove, offer, TRUE);
15705             DisplayComment(currentMove - 1, offer);
15706             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15707         } else {
15708             DisplayError(_("You must make your move before offering a draw"), 0);
15709             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15710         }
15711     } else if (first.offeredDraw) {
15712         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15713     } else {
15714         if (first.sendDrawOffers) {
15715             SendToProgram("draw\n", &first);
15716             userOfferedDraw = TRUE;
15717         }
15718     }
15719 }
15720
15721 void
15722 AdjournEvent ()
15723 {
15724     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15725
15726     if (appData.icsActive) {
15727         SendToICS(ics_prefix);
15728         SendToICS("adjourn\n");
15729     } else {
15730         /* Currently GNU Chess doesn't offer or accept Adjourns */
15731     }
15732 }
15733
15734
15735 void
15736 AbortEvent ()
15737 {
15738     /* Offer Abort or accept pending Abort offer from opponent */
15739
15740     if (appData.icsActive) {
15741         SendToICS(ics_prefix);
15742         SendToICS("abort\n");
15743     } else {
15744         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15745     }
15746 }
15747
15748 void
15749 ResignEvent ()
15750 {
15751     /* Resign.  You can do this even if it's not your turn. */
15752
15753     if (appData.icsActive) {
15754         SendToICS(ics_prefix);
15755         SendToICS("resign\n");
15756     } else {
15757         switch (gameMode) {
15758           case MachinePlaysWhite:
15759             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15760             break;
15761           case MachinePlaysBlack:
15762             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15763             break;
15764           case EditGame:
15765             if (cmailMsgLoaded) {
15766                 TruncateGame();
15767                 if (WhiteOnMove(cmailOldMove)) {
15768                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15769                 } else {
15770                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15771                 }
15772                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15773             }
15774             break;
15775           default:
15776             break;
15777         }
15778     }
15779 }
15780
15781
15782 void
15783 StopObservingEvent ()
15784 {
15785     /* Stop observing current games */
15786     SendToICS(ics_prefix);
15787     SendToICS("unobserve\n");
15788 }
15789
15790 void
15791 StopExaminingEvent ()
15792 {
15793     /* Stop observing current game */
15794     SendToICS(ics_prefix);
15795     SendToICS("unexamine\n");
15796 }
15797
15798 void
15799 ForwardInner (int target)
15800 {
15801     int limit; int oldSeekGraphUp = seekGraphUp;
15802
15803     if (appData.debugMode)
15804         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15805                 target, currentMove, forwardMostMove);
15806
15807     if (gameMode == EditPosition)
15808       return;
15809
15810     seekGraphUp = FALSE;
15811     MarkTargetSquares(1);
15812     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15813
15814     if (gameMode == PlayFromGameFile && !pausing)
15815       PauseEvent();
15816
15817     if (gameMode == IcsExamining && pausing)
15818       limit = pauseExamForwardMostMove;
15819     else
15820       limit = forwardMostMove;
15821
15822     if (target > limit) target = limit;
15823
15824     if (target > 0 && moveList[target - 1][0]) {
15825         int fromX, fromY, toX, toY;
15826         toX = moveList[target - 1][2] - AAA;
15827         toY = moveList[target - 1][3] - ONE;
15828         if (moveList[target - 1][1] == '@') {
15829             if (appData.highlightLastMove) {
15830                 SetHighlights(-1, -1, toX, toY);
15831             }
15832         } else {
15833             int viaX = moveList[target - 1][5] - AAA;
15834             int viaY = moveList[target - 1][6] - ONE;
15835             fromX = moveList[target - 1][0] - AAA;
15836             fromY = moveList[target - 1][1] - ONE;
15837             if (target == currentMove + 1) {
15838                 if(moveList[target - 1][4] == ';') { // multi-leg
15839                     ChessSquare piece = boards[currentMove][viaY][viaX];
15840                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15841                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15842                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15843                     boards[currentMove][viaY][viaX] = piece;
15844                 } else
15845                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15846             }
15847             if (appData.highlightLastMove) {
15848                 SetHighlights(fromX, fromY, toX, toY);
15849             }
15850         }
15851     }
15852     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15853         gameMode == Training || gameMode == PlayFromGameFile ||
15854         gameMode == AnalyzeFile) {
15855         while (currentMove < target) {
15856             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15857             SendMoveToProgram(currentMove++, &first);
15858         }
15859     } else {
15860         currentMove = target;
15861     }
15862
15863     if (gameMode == EditGame || gameMode == EndOfGame) {
15864         whiteTimeRemaining = timeRemaining[0][currentMove];
15865         blackTimeRemaining = timeRemaining[1][currentMove];
15866     }
15867     DisplayBothClocks();
15868     DisplayMove(currentMove - 1);
15869     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15870     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15871     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15872         DisplayComment(currentMove - 1, commentList[currentMove]);
15873     }
15874     ClearMap(); // [HGM] exclude: invalidate map
15875 }
15876
15877
15878 void
15879 ForwardEvent ()
15880 {
15881     if (gameMode == IcsExamining && !pausing) {
15882         SendToICS(ics_prefix);
15883         SendToICS("forward\n");
15884     } else {
15885         ForwardInner(currentMove + 1);
15886     }
15887 }
15888
15889 void
15890 ToEndEvent ()
15891 {
15892     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15893         /* to optimze, we temporarily turn off analysis mode while we feed
15894          * the remaining moves to the engine. Otherwise we get analysis output
15895          * after each move.
15896          */
15897         if (first.analysisSupport) {
15898           SendToProgram("exit\nforce\n", &first);
15899           first.analyzing = FALSE;
15900         }
15901     }
15902
15903     if (gameMode == IcsExamining && !pausing) {
15904         SendToICS(ics_prefix);
15905         SendToICS("forward 999999\n");
15906     } else {
15907         ForwardInner(forwardMostMove);
15908     }
15909
15910     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15911         /* we have fed all the moves, so reactivate analysis mode */
15912         SendToProgram("analyze\n", &first);
15913         first.analyzing = TRUE;
15914         /*first.maybeThinking = TRUE;*/
15915         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15916     }
15917 }
15918
15919 void
15920 BackwardInner (int target)
15921 {
15922     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15923
15924     if (appData.debugMode)
15925         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15926                 target, currentMove, forwardMostMove);
15927
15928     if (gameMode == EditPosition) return;
15929     seekGraphUp = FALSE;
15930     MarkTargetSquares(1);
15931     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15932     if (currentMove <= backwardMostMove) {
15933         ClearHighlights();
15934         DrawPosition(full_redraw, boards[currentMove]);
15935         return;
15936     }
15937     if (gameMode == PlayFromGameFile && !pausing)
15938       PauseEvent();
15939
15940     if (moveList[target][0]) {
15941         int fromX, fromY, toX, toY;
15942         toX = moveList[target][2] - AAA;
15943         toY = moveList[target][3] - ONE;
15944         if (moveList[target][1] == '@') {
15945             if (appData.highlightLastMove) {
15946                 SetHighlights(-1, -1, toX, toY);
15947             }
15948         } else {
15949             fromX = moveList[target][0] - AAA;
15950             fromY = moveList[target][1] - ONE;
15951             if (target == currentMove - 1) {
15952                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15953             }
15954             if (appData.highlightLastMove) {
15955                 SetHighlights(fromX, fromY, toX, toY);
15956             }
15957         }
15958     }
15959     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15960         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15961         while (currentMove > target) {
15962             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15963                 // null move cannot be undone. Reload program with move history before it.
15964                 int i;
15965                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15966                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15967                 }
15968                 SendBoard(&first, i);
15969               if(second.analyzing) SendBoard(&second, i);
15970                 for(currentMove=i; currentMove<target; currentMove++) {
15971                     SendMoveToProgram(currentMove, &first);
15972                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15973                 }
15974                 break;
15975             }
15976             SendToBoth("undo\n");
15977             currentMove--;
15978         }
15979     } else {
15980         currentMove = target;
15981     }
15982
15983     if (gameMode == EditGame || gameMode == EndOfGame) {
15984         whiteTimeRemaining = timeRemaining[0][currentMove];
15985         blackTimeRemaining = timeRemaining[1][currentMove];
15986     }
15987     DisplayBothClocks();
15988     DisplayMove(currentMove - 1);
15989     DrawPosition(full_redraw, boards[currentMove]);
15990     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15991     // [HGM] PV info: routine tests if comment empty
15992     DisplayComment(currentMove - 1, commentList[currentMove]);
15993     ClearMap(); // [HGM] exclude: invalidate map
15994 }
15995
15996 void
15997 BackwardEvent ()
15998 {
15999     if (gameMode == IcsExamining && !pausing) {
16000         SendToICS(ics_prefix);
16001         SendToICS("backward\n");
16002     } else {
16003         BackwardInner(currentMove - 1);
16004     }
16005 }
16006
16007 void
16008 ToStartEvent ()
16009 {
16010     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16011         /* to optimize, we temporarily turn off analysis mode while we undo
16012          * all the moves. Otherwise we get analysis output after each undo.
16013          */
16014         if (first.analysisSupport) {
16015           SendToProgram("exit\nforce\n", &first);
16016           first.analyzing = FALSE;
16017         }
16018     }
16019
16020     if (gameMode == IcsExamining && !pausing) {
16021         SendToICS(ics_prefix);
16022         SendToICS("backward 999999\n");
16023     } else {
16024         BackwardInner(backwardMostMove);
16025     }
16026
16027     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16028         /* we have fed all the moves, so reactivate analysis mode */
16029         SendToProgram("analyze\n", &first);
16030         first.analyzing = TRUE;
16031         /*first.maybeThinking = TRUE;*/
16032         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16033     }
16034 }
16035
16036 void
16037 ToNrEvent (int to)
16038 {
16039   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16040   if (to >= forwardMostMove) to = forwardMostMove;
16041   if (to <= backwardMostMove) to = backwardMostMove;
16042   if (to < currentMove) {
16043     BackwardInner(to);
16044   } else {
16045     ForwardInner(to);
16046   }
16047 }
16048
16049 void
16050 RevertEvent (Boolean annotate)
16051 {
16052     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16053         return;
16054     }
16055     if (gameMode != IcsExamining) {
16056         DisplayError(_("You are not examining a game"), 0);
16057         return;
16058     }
16059     if (pausing) {
16060         DisplayError(_("You can't revert while pausing"), 0);
16061         return;
16062     }
16063     SendToICS(ics_prefix);
16064     SendToICS("revert\n");
16065 }
16066
16067 void
16068 RetractMoveEvent ()
16069 {
16070     switch (gameMode) {
16071       case MachinePlaysWhite:
16072       case MachinePlaysBlack:
16073         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16074             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16075             return;
16076         }
16077         if (forwardMostMove < 2) return;
16078         currentMove = forwardMostMove = forwardMostMove - 2;
16079         whiteTimeRemaining = timeRemaining[0][currentMove];
16080         blackTimeRemaining = timeRemaining[1][currentMove];
16081         DisplayBothClocks();
16082         DisplayMove(currentMove - 1);
16083         ClearHighlights();/*!! could figure this out*/
16084         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16085         SendToProgram("remove\n", &first);
16086         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16087         break;
16088
16089       case BeginningOfGame:
16090       default:
16091         break;
16092
16093       case IcsPlayingWhite:
16094       case IcsPlayingBlack:
16095         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16096             SendToICS(ics_prefix);
16097             SendToICS("takeback 2\n");
16098         } else {
16099             SendToICS(ics_prefix);
16100             SendToICS("takeback 1\n");
16101         }
16102         break;
16103     }
16104 }
16105
16106 void
16107 MoveNowEvent ()
16108 {
16109     ChessProgramState *cps;
16110
16111     switch (gameMode) {
16112       case MachinePlaysWhite:
16113         if (!WhiteOnMove(forwardMostMove)) {
16114             DisplayError(_("It is your turn"), 0);
16115             return;
16116         }
16117         cps = &first;
16118         break;
16119       case MachinePlaysBlack:
16120         if (WhiteOnMove(forwardMostMove)) {
16121             DisplayError(_("It is your turn"), 0);
16122             return;
16123         }
16124         cps = &first;
16125         break;
16126       case TwoMachinesPlay:
16127         if (WhiteOnMove(forwardMostMove) ==
16128             (first.twoMachinesColor[0] == 'w')) {
16129             cps = &first;
16130         } else {
16131             cps = &second;
16132         }
16133         break;
16134       case BeginningOfGame:
16135       default:
16136         return;
16137     }
16138     SendToProgram("?\n", cps);
16139 }
16140
16141 void
16142 TruncateGameEvent ()
16143 {
16144     EditGameEvent();
16145     if (gameMode != EditGame) return;
16146     TruncateGame();
16147 }
16148
16149 void
16150 TruncateGame ()
16151 {
16152     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16153     if (forwardMostMove > currentMove) {
16154         if (gameInfo.resultDetails != NULL) {
16155             free(gameInfo.resultDetails);
16156             gameInfo.resultDetails = NULL;
16157             gameInfo.result = GameUnfinished;
16158         }
16159         forwardMostMove = currentMove;
16160         HistorySet(parseList, backwardMostMove, forwardMostMove,
16161                    currentMove-1);
16162     }
16163 }
16164
16165 void
16166 HintEvent ()
16167 {
16168     if (appData.noChessProgram) return;
16169     switch (gameMode) {
16170       case MachinePlaysWhite:
16171         if (WhiteOnMove(forwardMostMove)) {
16172             DisplayError(_("Wait until your turn."), 0);
16173             return;
16174         }
16175         break;
16176       case BeginningOfGame:
16177       case MachinePlaysBlack:
16178         if (!WhiteOnMove(forwardMostMove)) {
16179             DisplayError(_("Wait until your turn."), 0);
16180             return;
16181         }
16182         break;
16183       default:
16184         DisplayError(_("No hint available"), 0);
16185         return;
16186     }
16187     SendToProgram("hint\n", &first);
16188     hintRequested = TRUE;
16189 }
16190
16191 int
16192 SaveSelected (FILE *g, int dummy, char *dummy2)
16193 {
16194     ListGame * lg = (ListGame *) gameList.head;
16195     int nItem, cnt=0;
16196     FILE *f;
16197
16198     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16199         DisplayError(_("Game list not loaded or empty"), 0);
16200         return 0;
16201     }
16202
16203     creatingBook = TRUE; // suppresses stuff during load game
16204
16205     /* Get list size */
16206     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16207         if(lg->position >= 0) { // selected?
16208             LoadGame(f, nItem, "", TRUE);
16209             SaveGamePGN2(g); // leaves g open
16210             cnt++; DoEvents();
16211         }
16212         lg = (ListGame *) lg->node.succ;
16213     }
16214
16215     fclose(g);
16216     creatingBook = FALSE;
16217
16218     return cnt;
16219 }
16220
16221 void
16222 CreateBookEvent ()
16223 {
16224     ListGame * lg = (ListGame *) gameList.head;
16225     FILE *f, *g;
16226     int nItem;
16227     static int secondTime = FALSE;
16228
16229     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16230         DisplayError(_("Game list not loaded or empty"), 0);
16231         return;
16232     }
16233
16234     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16235         fclose(g);
16236         secondTime++;
16237         DisplayNote(_("Book file exists! Try again for overwrite."));
16238         return;
16239     }
16240
16241     creatingBook = TRUE;
16242     secondTime = FALSE;
16243
16244     /* Get list size */
16245     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16246         if(lg->position >= 0) {
16247             LoadGame(f, nItem, "", TRUE);
16248             AddGameToBook(TRUE);
16249             DoEvents();
16250         }
16251         lg = (ListGame *) lg->node.succ;
16252     }
16253
16254     creatingBook = FALSE;
16255     FlushBook();
16256 }
16257
16258 void
16259 BookEvent ()
16260 {
16261     if (appData.noChessProgram) return;
16262     switch (gameMode) {
16263       case MachinePlaysWhite:
16264         if (WhiteOnMove(forwardMostMove)) {
16265             DisplayError(_("Wait until your turn."), 0);
16266             return;
16267         }
16268         break;
16269       case BeginningOfGame:
16270       case MachinePlaysBlack:
16271         if (!WhiteOnMove(forwardMostMove)) {
16272             DisplayError(_("Wait until your turn."), 0);
16273             return;
16274         }
16275         break;
16276       case EditPosition:
16277         EditPositionDone(TRUE);
16278         break;
16279       case TwoMachinesPlay:
16280         return;
16281       default:
16282         break;
16283     }
16284     SendToProgram("bk\n", &first);
16285     bookOutput[0] = NULLCHAR;
16286     bookRequested = TRUE;
16287 }
16288
16289 void
16290 AboutGameEvent ()
16291 {
16292     char *tags = PGNTags(&gameInfo);
16293     TagsPopUp(tags, CmailMsg());
16294     free(tags);
16295 }
16296
16297 /* end button procedures */
16298
16299 void
16300 PrintPosition (FILE *fp, int move)
16301 {
16302     int i, j;
16303
16304     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16305         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16306             char c = PieceToChar(boards[move][i][j]);
16307             fputc(c == '?' ? '.' : c, fp);
16308             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16309         }
16310     }
16311     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16312       fprintf(fp, "white to play\n");
16313     else
16314       fprintf(fp, "black to play\n");
16315 }
16316
16317 void
16318 PrintOpponents (FILE *fp)
16319 {
16320     if (gameInfo.white != NULL) {
16321         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16322     } else {
16323         fprintf(fp, "\n");
16324     }
16325 }
16326
16327 /* Find last component of program's own name, using some heuristics */
16328 void
16329 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16330 {
16331     char *p, *q, c;
16332     int local = (strcmp(host, "localhost") == 0);
16333     while (!local && (p = strchr(prog, ';')) != NULL) {
16334         p++;
16335         while (*p == ' ') p++;
16336         prog = p;
16337     }
16338     if (*prog == '"' || *prog == '\'') {
16339         q = strchr(prog + 1, *prog);
16340     } else {
16341         q = strchr(prog, ' ');
16342     }
16343     if (q == NULL) q = prog + strlen(prog);
16344     p = q;
16345     while (p >= prog && *p != '/' && *p != '\\') p--;
16346     p++;
16347     if(p == prog && *p == '"') p++;
16348     c = *q; *q = 0;
16349     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16350     memcpy(buf, p, q - p);
16351     buf[q - p] = NULLCHAR;
16352     if (!local) {
16353         strcat(buf, "@");
16354         strcat(buf, host);
16355     }
16356 }
16357
16358 char *
16359 TimeControlTagValue ()
16360 {
16361     char buf[MSG_SIZ];
16362     if (!appData.clockMode) {
16363       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16364     } else if (movesPerSession > 0) {
16365       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16366     } else if (timeIncrement == 0) {
16367       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16368     } else {
16369       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16370     }
16371     return StrSave(buf);
16372 }
16373
16374 void
16375 SetGameInfo ()
16376 {
16377     /* This routine is used only for certain modes */
16378     VariantClass v = gameInfo.variant;
16379     ChessMove r = GameUnfinished;
16380     char *p = NULL;
16381
16382     if(keepInfo) return;
16383
16384     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16385         r = gameInfo.result;
16386         p = gameInfo.resultDetails;
16387         gameInfo.resultDetails = NULL;
16388     }
16389     ClearGameInfo(&gameInfo);
16390     gameInfo.variant = v;
16391
16392     switch (gameMode) {
16393       case MachinePlaysWhite:
16394         gameInfo.event = StrSave( appData.pgnEventHeader );
16395         gameInfo.site = StrSave(HostName());
16396         gameInfo.date = PGNDate();
16397         gameInfo.round = StrSave("-");
16398         gameInfo.white = StrSave(first.tidy);
16399         gameInfo.black = StrSave(UserName());
16400         gameInfo.timeControl = TimeControlTagValue();
16401         break;
16402
16403       case MachinePlaysBlack:
16404         gameInfo.event = StrSave( appData.pgnEventHeader );
16405         gameInfo.site = StrSave(HostName());
16406         gameInfo.date = PGNDate();
16407         gameInfo.round = StrSave("-");
16408         gameInfo.white = StrSave(UserName());
16409         gameInfo.black = StrSave(first.tidy);
16410         gameInfo.timeControl = TimeControlTagValue();
16411         break;
16412
16413       case TwoMachinesPlay:
16414         gameInfo.event = StrSave( appData.pgnEventHeader );
16415         gameInfo.site = StrSave(HostName());
16416         gameInfo.date = PGNDate();
16417         if (roundNr > 0) {
16418             char buf[MSG_SIZ];
16419             snprintf(buf, MSG_SIZ, "%d", roundNr);
16420             gameInfo.round = StrSave(buf);
16421         } else {
16422             gameInfo.round = StrSave("-");
16423         }
16424         if (first.twoMachinesColor[0] == 'w') {
16425             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16426             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16427         } else {
16428             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16429             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16430         }
16431         gameInfo.timeControl = TimeControlTagValue();
16432         break;
16433
16434       case EditGame:
16435         gameInfo.event = StrSave("Edited game");
16436         gameInfo.site = StrSave(HostName());
16437         gameInfo.date = PGNDate();
16438         gameInfo.round = StrSave("-");
16439         gameInfo.white = StrSave("-");
16440         gameInfo.black = StrSave("-");
16441         gameInfo.result = r;
16442         gameInfo.resultDetails = p;
16443         break;
16444
16445       case EditPosition:
16446         gameInfo.event = StrSave("Edited position");
16447         gameInfo.site = StrSave(HostName());
16448         gameInfo.date = PGNDate();
16449         gameInfo.round = StrSave("-");
16450         gameInfo.white = StrSave("-");
16451         gameInfo.black = StrSave("-");
16452         break;
16453
16454       case IcsPlayingWhite:
16455       case IcsPlayingBlack:
16456       case IcsObserving:
16457       case IcsExamining:
16458         break;
16459
16460       case PlayFromGameFile:
16461         gameInfo.event = StrSave("Game from non-PGN file");
16462         gameInfo.site = StrSave(HostName());
16463         gameInfo.date = PGNDate();
16464         gameInfo.round = StrSave("-");
16465         gameInfo.white = StrSave("?");
16466         gameInfo.black = StrSave("?");
16467         break;
16468
16469       default:
16470         break;
16471     }
16472 }
16473
16474 void
16475 ReplaceComment (int index, char *text)
16476 {
16477     int len;
16478     char *p;
16479     float score;
16480
16481     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16482        pvInfoList[index-1].depth == len &&
16483        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16484        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16485     while (*text == '\n') text++;
16486     len = strlen(text);
16487     while (len > 0 && text[len - 1] == '\n') len--;
16488
16489     if (commentList[index] != NULL)
16490       free(commentList[index]);
16491
16492     if (len == 0) {
16493         commentList[index] = NULL;
16494         return;
16495     }
16496   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16497       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16498       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16499     commentList[index] = (char *) malloc(len + 2);
16500     strncpy(commentList[index], text, len);
16501     commentList[index][len] = '\n';
16502     commentList[index][len + 1] = NULLCHAR;
16503   } else {
16504     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16505     char *p;
16506     commentList[index] = (char *) malloc(len + 7);
16507     safeStrCpy(commentList[index], "{\n", 3);
16508     safeStrCpy(commentList[index]+2, text, len+1);
16509     commentList[index][len+2] = NULLCHAR;
16510     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16511     strcat(commentList[index], "\n}\n");
16512   }
16513 }
16514
16515 void
16516 CrushCRs (char *text)
16517 {
16518   char *p = text;
16519   char *q = text;
16520   char ch;
16521
16522   do {
16523     ch = *p++;
16524     if (ch == '\r') continue;
16525     *q++ = ch;
16526   } while (ch != '\0');
16527 }
16528
16529 void
16530 AppendComment (int index, char *text, Boolean addBraces)
16531 /* addBraces  tells if we should add {} */
16532 {
16533     int oldlen, len;
16534     char *old;
16535
16536 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16537     if(addBraces == 3) addBraces = 0; else // force appending literally
16538     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16539
16540     CrushCRs(text);
16541     while (*text == '\n') text++;
16542     len = strlen(text);
16543     while (len > 0 && text[len - 1] == '\n') len--;
16544     text[len] = NULLCHAR;
16545
16546     if (len == 0) return;
16547
16548     if (commentList[index] != NULL) {
16549       Boolean addClosingBrace = addBraces;
16550         old = commentList[index];
16551         oldlen = strlen(old);
16552         while(commentList[index][oldlen-1] ==  '\n')
16553           commentList[index][--oldlen] = NULLCHAR;
16554         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16555         safeStrCpy(commentList[index], old, oldlen + len + 6);
16556         free(old);
16557         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16558         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16559           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16560           while (*text == '\n') { text++; len--; }
16561           commentList[index][--oldlen] = NULLCHAR;
16562       }
16563         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16564         else          strcat(commentList[index], "\n");
16565         strcat(commentList[index], text);
16566         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16567         else          strcat(commentList[index], "\n");
16568     } else {
16569         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16570         if(addBraces)
16571           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16572         else commentList[index][0] = NULLCHAR;
16573         strcat(commentList[index], text);
16574         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16575         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16576     }
16577 }
16578
16579 static char *
16580 FindStr (char * text, char * sub_text)
16581 {
16582     char * result = strstr( text, sub_text );
16583
16584     if( result != NULL ) {
16585         result += strlen( sub_text );
16586     }
16587
16588     return result;
16589 }
16590
16591 /* [AS] Try to extract PV info from PGN comment */
16592 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16593 char *
16594 GetInfoFromComment (int index, char * text)
16595 {
16596     char * sep = text, *p;
16597
16598     if( text != NULL && index > 0 ) {
16599         int score = 0;
16600         int depth = 0;
16601         int time = -1, sec = 0, deci;
16602         char * s_eval = FindStr( text, "[%eval " );
16603         char * s_emt = FindStr( text, "[%emt " );
16604 #if 0
16605         if( s_eval != NULL || s_emt != NULL ) {
16606 #else
16607         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16608 #endif
16609             /* New style */
16610             char delim;
16611
16612             if( s_eval != NULL ) {
16613                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16614                     return text;
16615                 }
16616
16617                 if( delim != ']' ) {
16618                     return text;
16619                 }
16620             }
16621
16622             if( s_emt != NULL ) {
16623             }
16624                 return text;
16625         }
16626         else {
16627             /* We expect something like: [+|-]nnn.nn/dd */
16628             int score_lo = 0;
16629
16630             if(*text != '{') return text; // [HGM] braces: must be normal comment
16631
16632             sep = strchr( text, '/' );
16633             if( sep == NULL || sep < (text+4) ) {
16634                 return text;
16635             }
16636
16637             p = text;
16638             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16639             if(p[1] == '(') { // comment starts with PV
16640                p = strchr(p, ')'); // locate end of PV
16641                if(p == NULL || sep < p+5) return text;
16642                // at this point we have something like "{(.*) +0.23/6 ..."
16643                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16644                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16645                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16646             }
16647             time = -1; sec = -1; deci = -1;
16648             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16649                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16650                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16651                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16652                 return text;
16653             }
16654
16655             if( score_lo < 0 || score_lo >= 100 ) {
16656                 return text;
16657             }
16658
16659             if(sec >= 0) time = 600*time + 10*sec; else
16660             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16661
16662             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16663
16664             /* [HGM] PV time: now locate end of PV info */
16665             while( *++sep >= '0' && *sep <= '9'); // strip depth
16666             if(time >= 0)
16667             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16668             if(sec >= 0)
16669             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16670             if(deci >= 0)
16671             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16672             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16673         }
16674
16675         if( depth <= 0 ) {
16676             return text;
16677         }
16678
16679         if( time < 0 ) {
16680             time = -1;
16681         }
16682
16683         pvInfoList[index-1].depth = depth;
16684         pvInfoList[index-1].score = score;
16685         pvInfoList[index-1].time  = 10*time; // centi-sec
16686         if(*sep == '}') *sep = 0; else *--sep = '{';
16687         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16688     }
16689     return sep;
16690 }
16691
16692 void
16693 SendToProgram (char *message, ChessProgramState *cps)
16694 {
16695     int count, outCount, error;
16696     char buf[MSG_SIZ];
16697
16698     if (cps->pr == NoProc) return;
16699     Attention(cps);
16700
16701     if (appData.debugMode) {
16702         TimeMark now;
16703         GetTimeMark(&now);
16704         fprintf(debugFP, "%ld >%-6s: %s",
16705                 SubtractTimeMarks(&now, &programStartTime),
16706                 cps->which, message);
16707         if(serverFP)
16708             fprintf(serverFP, "%ld >%-6s: %s",
16709                 SubtractTimeMarks(&now, &programStartTime),
16710                 cps->which, message), fflush(serverFP);
16711     }
16712
16713     count = strlen(message);
16714     outCount = OutputToProcess(cps->pr, message, count, &error);
16715     if (outCount < count && !exiting
16716                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16717       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16718       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16719         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16720             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16721                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16722                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16723                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16724             } else {
16725                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16726                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16727                 gameInfo.result = res;
16728             }
16729             gameInfo.resultDetails = StrSave(buf);
16730         }
16731         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16732         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16733     }
16734 }
16735
16736 void
16737 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16738 {
16739     char *end_str;
16740     char buf[MSG_SIZ];
16741     ChessProgramState *cps = (ChessProgramState *)closure;
16742
16743     if (isr != cps->isr) return; /* Killed intentionally */
16744     if (count <= 0) {
16745         if (count == 0) {
16746             RemoveInputSource(cps->isr);
16747             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16748                     _(cps->which), cps->program);
16749             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16750             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16751                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16752                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16753                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16754                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16755                 } else {
16756                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16757                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16758                     gameInfo.result = res;
16759                 }
16760                 gameInfo.resultDetails = StrSave(buf);
16761             }
16762             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16763             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16764         } else {
16765             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16766                     _(cps->which), cps->program);
16767             RemoveInputSource(cps->isr);
16768
16769             /* [AS] Program is misbehaving badly... kill it */
16770             if( count == -2 ) {
16771                 DestroyChildProcess( cps->pr, 9 );
16772                 cps->pr = NoProc;
16773             }
16774
16775             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16776         }
16777         return;
16778     }
16779
16780     if ((end_str = strchr(message, '\r')) != NULL)
16781       *end_str = NULLCHAR;
16782     if ((end_str = strchr(message, '\n')) != NULL)
16783       *end_str = NULLCHAR;
16784
16785     if (appData.debugMode) {
16786         TimeMark now; int print = 1;
16787         char *quote = ""; char c; int i;
16788
16789         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16790                 char start = message[0];
16791                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16792                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16793                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16794                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16795                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16796                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16797                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16798                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16799                    sscanf(message, "hint: %c", &c)!=1 &&
16800                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16801                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16802                     print = (appData.engineComments >= 2);
16803                 }
16804                 message[0] = start; // restore original message
16805         }
16806         if(print) {
16807                 GetTimeMark(&now);
16808                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16809                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16810                         quote,
16811                         message);
16812                 if(serverFP)
16813                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16814                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16815                         quote,
16816                         message), fflush(serverFP);
16817         }
16818     }
16819
16820     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16821     if (appData.icsEngineAnalyze) {
16822         if (strstr(message, "whisper") != NULL ||
16823              strstr(message, "kibitz") != NULL ||
16824             strstr(message, "tellics") != NULL) return;
16825     }
16826
16827     HandleMachineMove(message, cps);
16828 }
16829
16830
16831 void
16832 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16833 {
16834     char buf[MSG_SIZ];
16835     int seconds;
16836
16837     if( timeControl_2 > 0 ) {
16838         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16839             tc = timeControl_2;
16840         }
16841     }
16842     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16843     inc /= cps->timeOdds;
16844     st  /= cps->timeOdds;
16845
16846     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16847
16848     if (st > 0) {
16849       /* Set exact time per move, normally using st command */
16850       if (cps->stKludge) {
16851         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16852         seconds = st % 60;
16853         if (seconds == 0) {
16854           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16855         } else {
16856           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16857         }
16858       } else {
16859         snprintf(buf, MSG_SIZ, "st %d\n", st);
16860       }
16861     } else {
16862       /* Set conventional or incremental time control, using level command */
16863       if (seconds == 0) {
16864         /* Note old gnuchess bug -- minutes:seconds used to not work.
16865            Fixed in later versions, but still avoid :seconds
16866            when seconds is 0. */
16867         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16868       } else {
16869         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16870                  seconds, inc/1000.);
16871       }
16872     }
16873     SendToProgram(buf, cps);
16874
16875     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16876     /* Orthogonally, limit search to given depth */
16877     if (sd > 0) {
16878       if (cps->sdKludge) {
16879         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16880       } else {
16881         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16882       }
16883       SendToProgram(buf, cps);
16884     }
16885
16886     if(cps->nps >= 0) { /* [HGM] nps */
16887         if(cps->supportsNPS == FALSE)
16888           cps->nps = -1; // don't use if engine explicitly says not supported!
16889         else {
16890           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16891           SendToProgram(buf, cps);
16892         }
16893     }
16894 }
16895
16896 ChessProgramState *
16897 WhitePlayer ()
16898 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16899 {
16900     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16901        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16902         return &second;
16903     return &first;
16904 }
16905
16906 void
16907 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16908 {
16909     char message[MSG_SIZ];
16910     long time, otime;
16911
16912     /* Note: this routine must be called when the clocks are stopped
16913        or when they have *just* been set or switched; otherwise
16914        it will be off by the time since the current tick started.
16915     */
16916     if (machineWhite) {
16917         time = whiteTimeRemaining / 10;
16918         otime = blackTimeRemaining / 10;
16919     } else {
16920         time = blackTimeRemaining / 10;
16921         otime = whiteTimeRemaining / 10;
16922     }
16923     /* [HGM] translate opponent's time by time-odds factor */
16924     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16925
16926     if (time <= 0) time = 1;
16927     if (otime <= 0) otime = 1;
16928
16929     snprintf(message, MSG_SIZ, "time %ld\n", time);
16930     SendToProgram(message, cps);
16931
16932     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16933     SendToProgram(message, cps);
16934 }
16935
16936 char *
16937 EngineDefinedVariant (ChessProgramState *cps, int n)
16938 {   // return name of n-th unknown variant that engine supports
16939     static char buf[MSG_SIZ];
16940     char *p, *s = cps->variants;
16941     if(!s) return NULL;
16942     do { // parse string from variants feature
16943       VariantClass v;
16944         p = strchr(s, ',');
16945         if(p) *p = NULLCHAR;
16946       v = StringToVariant(s);
16947       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16948         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16949             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16950                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16951                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16952                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16953             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16954         }
16955         if(p) *p++ = ',';
16956         if(n < 0) return buf;
16957     } while(s = p);
16958     return NULL;
16959 }
16960
16961 int
16962 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16963 {
16964   char buf[MSG_SIZ];
16965   int len = strlen(name);
16966   int val;
16967
16968   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16969     (*p) += len + 1;
16970     sscanf(*p, "%d", &val);
16971     *loc = (val != 0);
16972     while (**p && **p != ' ')
16973       (*p)++;
16974     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16975     SendToProgram(buf, cps);
16976     return TRUE;
16977   }
16978   return FALSE;
16979 }
16980
16981 int
16982 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16983 {
16984   char buf[MSG_SIZ];
16985   int len = strlen(name);
16986   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16987     (*p) += len + 1;
16988     sscanf(*p, "%d", loc);
16989     while (**p && **p != ' ') (*p)++;
16990     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16991     SendToProgram(buf, cps);
16992     return TRUE;
16993   }
16994   return FALSE;
16995 }
16996
16997 int
16998 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16999 {
17000   char buf[MSG_SIZ];
17001   int len = strlen(name);
17002   if (strncmp((*p), name, len) == 0
17003       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17004     (*p) += len + 2;
17005     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17006     sscanf(*p, "%[^\"]", *loc);
17007     while (**p && **p != '\"') (*p)++;
17008     if (**p == '\"') (*p)++;
17009     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17010     SendToProgram(buf, cps);
17011     return TRUE;
17012   }
17013   return FALSE;
17014 }
17015
17016 int
17017 ParseOption (Option *opt, ChessProgramState *cps)
17018 // [HGM] options: process the string that defines an engine option, and determine
17019 // name, type, default value, and allowed value range
17020 {
17021         char *p, *q, buf[MSG_SIZ];
17022         int n, min = (-1)<<31, max = 1<<31, def;
17023
17024         opt->target = &opt->value;   // OK for spin/slider and checkbox
17025         if(p = strstr(opt->name, " -spin ")) {
17026             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17027             if(max < min) max = min; // enforce consistency
17028             if(def < min) def = min;
17029             if(def > max) def = max;
17030             opt->value = def;
17031             opt->min = min;
17032             opt->max = max;
17033             opt->type = Spin;
17034         } else if((p = strstr(opt->name, " -slider "))) {
17035             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17036             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17037             if(max < min) max = min; // enforce consistency
17038             if(def < min) def = min;
17039             if(def > max) def = max;
17040             opt->value = def;
17041             opt->min = min;
17042             opt->max = max;
17043             opt->type = Spin; // Slider;
17044         } else if((p = strstr(opt->name, " -string "))) {
17045             opt->textValue = p+9;
17046             opt->type = TextBox;
17047             opt->target = &opt->textValue;
17048         } else if((p = strstr(opt->name, " -file "))) {
17049             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17050             opt->target = opt->textValue = p+7;
17051             opt->type = FileName; // FileName;
17052             opt->target = &opt->textValue;
17053         } else if((p = strstr(opt->name, " -path "))) {
17054             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17055             opt->target = opt->textValue = p+7;
17056             opt->type = PathName; // PathName;
17057             opt->target = &opt->textValue;
17058         } else if(p = strstr(opt->name, " -check ")) {
17059             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17060             opt->value = (def != 0);
17061             opt->type = CheckBox;
17062         } else if(p = strstr(opt->name, " -combo ")) {
17063             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17064             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17065             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17066             opt->value = n = 0;
17067             while(q = StrStr(q, " /// ")) {
17068                 n++; *q = 0;    // count choices, and null-terminate each of them
17069                 q += 5;
17070                 if(*q == '*') { // remember default, which is marked with * prefix
17071                     q++;
17072                     opt->value = n;
17073                 }
17074                 cps->comboList[cps->comboCnt++] = q;
17075             }
17076             cps->comboList[cps->comboCnt++] = NULL;
17077             opt->max = n + 1;
17078             opt->type = ComboBox;
17079         } else if(p = strstr(opt->name, " -button")) {
17080             opt->type = Button;
17081         } else if(p = strstr(opt->name, " -save")) {
17082             opt->type = SaveButton;
17083         } else return FALSE;
17084         *p = 0; // terminate option name
17085         // now look if the command-line options define a setting for this engine option.
17086         if(cps->optionSettings && cps->optionSettings[0])
17087             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17088         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17089           snprintf(buf, MSG_SIZ, "option %s", p);
17090                 if(p = strstr(buf, ",")) *p = 0;
17091                 if(q = strchr(buf, '=')) switch(opt->type) {
17092                     case ComboBox:
17093                         for(n=0; n<opt->max; n++)
17094                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17095                         break;
17096                     case TextBox:
17097                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17098                         break;
17099                     case Spin:
17100                     case CheckBox:
17101                         opt->value = atoi(q+1);
17102                     default:
17103                         break;
17104                 }
17105                 strcat(buf, "\n");
17106                 SendToProgram(buf, cps);
17107         }
17108         return TRUE;
17109 }
17110
17111 void
17112 FeatureDone (ChessProgramState *cps, int val)
17113 {
17114   DelayedEventCallback cb = GetDelayedEvent();
17115   if ((cb == InitBackEnd3 && cps == &first) ||
17116       (cb == SettingsMenuIfReady && cps == &second) ||
17117       (cb == LoadEngine) ||
17118       (cb == TwoMachinesEventIfReady)) {
17119     CancelDelayedEvent();
17120     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17121   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17122   cps->initDone = val;
17123   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17124 }
17125
17126 /* Parse feature command from engine */
17127 void
17128 ParseFeatures (char *args, ChessProgramState *cps)
17129 {
17130   char *p = args;
17131   char *q = NULL;
17132   int val;
17133   char buf[MSG_SIZ];
17134
17135   for (;;) {
17136     while (*p == ' ') p++;
17137     if (*p == NULLCHAR) return;
17138
17139     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17140     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17141     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17142     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17143     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17144     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17145     if (BoolFeature(&p, "reuse", &val, cps)) {
17146       /* Engine can disable reuse, but can't enable it if user said no */
17147       if (!val) cps->reuse = FALSE;
17148       continue;
17149     }
17150     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17151     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17152       if (gameMode == TwoMachinesPlay) {
17153         DisplayTwoMachinesTitle();
17154       } else {
17155         DisplayTitle("");
17156       }
17157       continue;
17158     }
17159     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17160     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17161     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17162     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17163     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17164     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17165     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17166     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17167     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17168     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17169     if (IntFeature(&p, "done", &val, cps)) {
17170       FeatureDone(cps, val);
17171       continue;
17172     }
17173     /* Added by Tord: */
17174     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17175     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17176     /* End of additions by Tord */
17177
17178     /* [HGM] added features: */
17179     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17180     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17181     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17182     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17183     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17184     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17185     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17186     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17187         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17188         FREE(cps->option[cps->nrOptions].name);
17189         cps->option[cps->nrOptions].name = q; q = NULL;
17190         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17191           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17192             SendToProgram(buf, cps);
17193             continue;
17194         }
17195         if(cps->nrOptions >= MAX_OPTIONS) {
17196             cps->nrOptions--;
17197             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17198             DisplayError(buf, 0);
17199         }
17200         continue;
17201     }
17202     /* End of additions by HGM */
17203
17204     /* unknown feature: complain and skip */
17205     q = p;
17206     while (*q && *q != '=') q++;
17207     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17208     SendToProgram(buf, cps);
17209     p = q;
17210     if (*p == '=') {
17211       p++;
17212       if (*p == '\"') {
17213         p++;
17214         while (*p && *p != '\"') p++;
17215         if (*p == '\"') p++;
17216       } else {
17217         while (*p && *p != ' ') p++;
17218       }
17219     }
17220   }
17221
17222 }
17223
17224 void
17225 PeriodicUpdatesEvent (int newState)
17226 {
17227     if (newState == appData.periodicUpdates)
17228       return;
17229
17230     appData.periodicUpdates=newState;
17231
17232     /* Display type changes, so update it now */
17233 //    DisplayAnalysis();
17234
17235     /* Get the ball rolling again... */
17236     if (newState) {
17237         AnalysisPeriodicEvent(1);
17238         StartAnalysisClock();
17239     }
17240 }
17241
17242 void
17243 PonderNextMoveEvent (int newState)
17244 {
17245     if (newState == appData.ponderNextMove) return;
17246     if (gameMode == EditPosition) EditPositionDone(TRUE);
17247     if (newState) {
17248         SendToProgram("hard\n", &first);
17249         if (gameMode == TwoMachinesPlay) {
17250             SendToProgram("hard\n", &second);
17251         }
17252     } else {
17253         SendToProgram("easy\n", &first);
17254         thinkOutput[0] = NULLCHAR;
17255         if (gameMode == TwoMachinesPlay) {
17256             SendToProgram("easy\n", &second);
17257         }
17258     }
17259     appData.ponderNextMove = newState;
17260 }
17261
17262 void
17263 NewSettingEvent (int option, int *feature, char *command, int value)
17264 {
17265     char buf[MSG_SIZ];
17266
17267     if (gameMode == EditPosition) EditPositionDone(TRUE);
17268     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17269     if(feature == NULL || *feature) SendToProgram(buf, &first);
17270     if (gameMode == TwoMachinesPlay) {
17271         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17272     }
17273 }
17274
17275 void
17276 ShowThinkingEvent ()
17277 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17278 {
17279     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17280     int newState = appData.showThinking
17281         // [HGM] thinking: other features now need thinking output as well
17282         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17283
17284     if (oldState == newState) return;
17285     oldState = newState;
17286     if (gameMode == EditPosition) EditPositionDone(TRUE);
17287     if (oldState) {
17288         SendToProgram("post\n", &first);
17289         if (gameMode == TwoMachinesPlay) {
17290             SendToProgram("post\n", &second);
17291         }
17292     } else {
17293         SendToProgram("nopost\n", &first);
17294         thinkOutput[0] = NULLCHAR;
17295         if (gameMode == TwoMachinesPlay) {
17296             SendToProgram("nopost\n", &second);
17297         }
17298     }
17299 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17300 }
17301
17302 void
17303 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17304 {
17305   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17306   if (pr == NoProc) return;
17307   AskQuestion(title, question, replyPrefix, pr);
17308 }
17309
17310 void
17311 TypeInEvent (char firstChar)
17312 {
17313     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17314         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17315         gameMode == AnalyzeMode || gameMode == EditGame ||
17316         gameMode == EditPosition || gameMode == IcsExamining ||
17317         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17318         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17319                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17320                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17321         gameMode == Training) PopUpMoveDialog(firstChar);
17322 }
17323
17324 void
17325 TypeInDoneEvent (char *move)
17326 {
17327         Board board;
17328         int n, fromX, fromY, toX, toY;
17329         char promoChar;
17330         ChessMove moveType;
17331
17332         // [HGM] FENedit
17333         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17334                 EditPositionPasteFEN(move);
17335                 return;
17336         }
17337         // [HGM] movenum: allow move number to be typed in any mode
17338         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17339           ToNrEvent(2*n-1);
17340           return;
17341         }
17342         // undocumented kludge: allow command-line option to be typed in!
17343         // (potentially fatal, and does not implement the effect of the option.)
17344         // should only be used for options that are values on which future decisions will be made,
17345         // and definitely not on options that would be used during initialization.
17346         if(strstr(move, "!!! -") == move) {
17347             ParseArgsFromString(move+4);
17348             return;
17349         }
17350
17351       if (gameMode != EditGame && currentMove != forwardMostMove &&
17352         gameMode != Training) {
17353         DisplayMoveError(_("Displayed move is not current"));
17354       } else {
17355         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17356           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17357         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17358         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17359           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17360           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17361         } else {
17362           DisplayMoveError(_("Could not parse move"));
17363         }
17364       }
17365 }
17366
17367 void
17368 DisplayMove (int moveNumber)
17369 {
17370     char message[MSG_SIZ];
17371     char res[MSG_SIZ];
17372     char cpThinkOutput[MSG_SIZ];
17373
17374     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17375
17376     if (moveNumber == forwardMostMove - 1 ||
17377         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17378
17379         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17380
17381         if (strchr(cpThinkOutput, '\n')) {
17382             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17383         }
17384     } else {
17385         *cpThinkOutput = NULLCHAR;
17386     }
17387
17388     /* [AS] Hide thinking from human user */
17389     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17390         *cpThinkOutput = NULLCHAR;
17391         if( thinkOutput[0] != NULLCHAR ) {
17392             int i;
17393
17394             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17395                 cpThinkOutput[i] = '.';
17396             }
17397             cpThinkOutput[i] = NULLCHAR;
17398             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17399         }
17400     }
17401
17402     if (moveNumber == forwardMostMove - 1 &&
17403         gameInfo.resultDetails != NULL) {
17404         if (gameInfo.resultDetails[0] == NULLCHAR) {
17405           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17406         } else {
17407           snprintf(res, MSG_SIZ, " {%s} %s",
17408                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17409         }
17410     } else {
17411         res[0] = NULLCHAR;
17412     }
17413
17414     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17415         DisplayMessage(res, cpThinkOutput);
17416     } else {
17417       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17418                 WhiteOnMove(moveNumber) ? " " : ".. ",
17419                 parseList[moveNumber], res);
17420         DisplayMessage(message, cpThinkOutput);
17421     }
17422 }
17423
17424 void
17425 DisplayComment (int moveNumber, char *text)
17426 {
17427     char title[MSG_SIZ];
17428
17429     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17430       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17431     } else {
17432       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17433               WhiteOnMove(moveNumber) ? " " : ".. ",
17434               parseList[moveNumber]);
17435     }
17436     if (text != NULL && (appData.autoDisplayComment || commentUp))
17437         CommentPopUp(title, text);
17438 }
17439
17440 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17441  * might be busy thinking or pondering.  It can be omitted if your
17442  * gnuchess is configured to stop thinking immediately on any user
17443  * input.  However, that gnuchess feature depends on the FIONREAD
17444  * ioctl, which does not work properly on some flavors of Unix.
17445  */
17446 void
17447 Attention (ChessProgramState *cps)
17448 {
17449 #if ATTENTION
17450     if (!cps->useSigint) return;
17451     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17452     switch (gameMode) {
17453       case MachinePlaysWhite:
17454       case MachinePlaysBlack:
17455       case TwoMachinesPlay:
17456       case IcsPlayingWhite:
17457       case IcsPlayingBlack:
17458       case AnalyzeMode:
17459       case AnalyzeFile:
17460         /* Skip if we know it isn't thinking */
17461         if (!cps->maybeThinking) return;
17462         if (appData.debugMode)
17463           fprintf(debugFP, "Interrupting %s\n", cps->which);
17464         InterruptChildProcess(cps->pr);
17465         cps->maybeThinking = FALSE;
17466         break;
17467       default:
17468         break;
17469     }
17470 #endif /*ATTENTION*/
17471 }
17472
17473 int
17474 CheckFlags ()
17475 {
17476     if (whiteTimeRemaining <= 0) {
17477         if (!whiteFlag) {
17478             whiteFlag = TRUE;
17479             if (appData.icsActive) {
17480                 if (appData.autoCallFlag &&
17481                     gameMode == IcsPlayingBlack && !blackFlag) {
17482                   SendToICS(ics_prefix);
17483                   SendToICS("flag\n");
17484                 }
17485             } else {
17486                 if (blackFlag) {
17487                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17488                 } else {
17489                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17490                     if (appData.autoCallFlag) {
17491                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17492                         return TRUE;
17493                     }
17494                 }
17495             }
17496         }
17497     }
17498     if (blackTimeRemaining <= 0) {
17499         if (!blackFlag) {
17500             blackFlag = TRUE;
17501             if (appData.icsActive) {
17502                 if (appData.autoCallFlag &&
17503                     gameMode == IcsPlayingWhite && !whiteFlag) {
17504                   SendToICS(ics_prefix);
17505                   SendToICS("flag\n");
17506                 }
17507             } else {
17508                 if (whiteFlag) {
17509                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17510                 } else {
17511                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17512                     if (appData.autoCallFlag) {
17513                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17514                         return TRUE;
17515                     }
17516                 }
17517             }
17518         }
17519     }
17520     return FALSE;
17521 }
17522
17523 void
17524 CheckTimeControl ()
17525 {
17526     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17527         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17528
17529     /*
17530      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17531      */
17532     if ( !WhiteOnMove(forwardMostMove) ) {
17533         /* White made time control */
17534         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17535         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17536         /* [HGM] time odds: correct new time quota for time odds! */
17537                                             / WhitePlayer()->timeOdds;
17538         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17539     } else {
17540         lastBlack -= blackTimeRemaining;
17541         /* Black made time control */
17542         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17543                                             / WhitePlayer()->other->timeOdds;
17544         lastWhite = whiteTimeRemaining;
17545     }
17546 }
17547
17548 void
17549 DisplayBothClocks ()
17550 {
17551     int wom = gameMode == EditPosition ?
17552       !blackPlaysFirst : WhiteOnMove(currentMove);
17553     DisplayWhiteClock(whiteTimeRemaining, wom);
17554     DisplayBlackClock(blackTimeRemaining, !wom);
17555 }
17556
17557
17558 /* Timekeeping seems to be a portability nightmare.  I think everyone
17559    has ftime(), but I'm really not sure, so I'm including some ifdefs
17560    to use other calls if you don't.  Clocks will be less accurate if
17561    you have neither ftime nor gettimeofday.
17562 */
17563
17564 /* VS 2008 requires the #include outside of the function */
17565 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17566 #include <sys/timeb.h>
17567 #endif
17568
17569 /* Get the current time as a TimeMark */
17570 void
17571 GetTimeMark (TimeMark *tm)
17572 {
17573 #if HAVE_GETTIMEOFDAY
17574
17575     struct timeval timeVal;
17576     struct timezone timeZone;
17577
17578     gettimeofday(&timeVal, &timeZone);
17579     tm->sec = (long) timeVal.tv_sec;
17580     tm->ms = (int) (timeVal.tv_usec / 1000L);
17581
17582 #else /*!HAVE_GETTIMEOFDAY*/
17583 #if HAVE_FTIME
17584
17585 // include <sys/timeb.h> / moved to just above start of function
17586     struct timeb timeB;
17587
17588     ftime(&timeB);
17589     tm->sec = (long) timeB.time;
17590     tm->ms = (int) timeB.millitm;
17591
17592 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17593     tm->sec = (long) time(NULL);
17594     tm->ms = 0;
17595 #endif
17596 #endif
17597 }
17598
17599 /* Return the difference in milliseconds between two
17600    time marks.  We assume the difference will fit in a long!
17601 */
17602 long
17603 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17604 {
17605     return 1000L*(tm2->sec - tm1->sec) +
17606            (long) (tm2->ms - tm1->ms);
17607 }
17608
17609
17610 /*
17611  * Code to manage the game clocks.
17612  *
17613  * In tournament play, black starts the clock and then white makes a move.
17614  * We give the human user a slight advantage if he is playing white---the
17615  * clocks don't run until he makes his first move, so it takes zero time.
17616  * Also, we don't account for network lag, so we could get out of sync
17617  * with GNU Chess's clock -- but then, referees are always right.
17618  */
17619
17620 static TimeMark tickStartTM;
17621 static long intendedTickLength;
17622
17623 long
17624 NextTickLength (long timeRemaining)
17625 {
17626     long nominalTickLength, nextTickLength;
17627
17628     if (timeRemaining > 0L && timeRemaining <= 10000L)
17629       nominalTickLength = 100L;
17630     else
17631       nominalTickLength = 1000L;
17632     nextTickLength = timeRemaining % nominalTickLength;
17633     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17634
17635     return nextTickLength;
17636 }
17637
17638 /* Adjust clock one minute up or down */
17639 void
17640 AdjustClock (Boolean which, int dir)
17641 {
17642     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17643     if(which) blackTimeRemaining += 60000*dir;
17644     else      whiteTimeRemaining += 60000*dir;
17645     DisplayBothClocks();
17646     adjustedClock = TRUE;
17647 }
17648
17649 /* Stop clocks and reset to a fresh time control */
17650 void
17651 ResetClocks ()
17652 {
17653     (void) StopClockTimer();
17654     if (appData.icsActive) {
17655         whiteTimeRemaining = blackTimeRemaining = 0;
17656     } else if (searchTime) {
17657         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17658         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17659     } else { /* [HGM] correct new time quote for time odds */
17660         whiteTC = blackTC = fullTimeControlString;
17661         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17662         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17663     }
17664     if (whiteFlag || blackFlag) {
17665         DisplayTitle("");
17666         whiteFlag = blackFlag = FALSE;
17667     }
17668     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17669     DisplayBothClocks();
17670     adjustedClock = FALSE;
17671 }
17672
17673 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17674
17675 /* Decrement running clock by amount of time that has passed */
17676 void
17677 DecrementClocks ()
17678 {
17679     long timeRemaining;
17680     long lastTickLength, fudge;
17681     TimeMark now;
17682
17683     if (!appData.clockMode) return;
17684     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17685
17686     GetTimeMark(&now);
17687
17688     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17689
17690     /* Fudge if we woke up a little too soon */
17691     fudge = intendedTickLength - lastTickLength;
17692     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17693
17694     if (WhiteOnMove(forwardMostMove)) {
17695         if(whiteNPS >= 0) lastTickLength = 0;
17696         timeRemaining = whiteTimeRemaining -= lastTickLength;
17697         if(timeRemaining < 0 && !appData.icsActive) {
17698             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17699             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17700                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17701                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17702             }
17703         }
17704         DisplayWhiteClock(whiteTimeRemaining - fudge,
17705                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17706     } else {
17707         if(blackNPS >= 0) lastTickLength = 0;
17708         timeRemaining = blackTimeRemaining -= lastTickLength;
17709         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17710             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17711             if(suddenDeath) {
17712                 blackStartMove = forwardMostMove;
17713                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17714             }
17715         }
17716         DisplayBlackClock(blackTimeRemaining - fudge,
17717                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17718     }
17719     if (CheckFlags()) return;
17720
17721     if(twoBoards) { // count down secondary board's clocks as well
17722         activePartnerTime -= lastTickLength;
17723         partnerUp = 1;
17724         if(activePartner == 'W')
17725             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17726         else
17727             DisplayBlackClock(activePartnerTime, TRUE);
17728         partnerUp = 0;
17729     }
17730
17731     tickStartTM = now;
17732     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17733     StartClockTimer(intendedTickLength);
17734
17735     /* if the time remaining has fallen below the alarm threshold, sound the
17736      * alarm. if the alarm has sounded and (due to a takeback or time control
17737      * with increment) the time remaining has increased to a level above the
17738      * threshold, reset the alarm so it can sound again.
17739      */
17740
17741     if (appData.icsActive && appData.icsAlarm) {
17742
17743         /* make sure we are dealing with the user's clock */
17744         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17745                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17746            )) return;
17747
17748         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17749             alarmSounded = FALSE;
17750         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17751             PlayAlarmSound();
17752             alarmSounded = TRUE;
17753         }
17754     }
17755 }
17756
17757
17758 /* A player has just moved, so stop the previously running
17759    clock and (if in clock mode) start the other one.
17760    We redisplay both clocks in case we're in ICS mode, because
17761    ICS gives us an update to both clocks after every move.
17762    Note that this routine is called *after* forwardMostMove
17763    is updated, so the last fractional tick must be subtracted
17764    from the color that is *not* on move now.
17765 */
17766 void
17767 SwitchClocks (int newMoveNr)
17768 {
17769     long lastTickLength;
17770     TimeMark now;
17771     int flagged = FALSE;
17772
17773     GetTimeMark(&now);
17774
17775     if (StopClockTimer() && appData.clockMode) {
17776         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17777         if (!WhiteOnMove(forwardMostMove)) {
17778             if(blackNPS >= 0) lastTickLength = 0;
17779             blackTimeRemaining -= lastTickLength;
17780            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17781 //         if(pvInfoList[forwardMostMove].time == -1)
17782                  pvInfoList[forwardMostMove].time =               // use GUI time
17783                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17784         } else {
17785            if(whiteNPS >= 0) lastTickLength = 0;
17786            whiteTimeRemaining -= lastTickLength;
17787            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17788 //         if(pvInfoList[forwardMostMove].time == -1)
17789                  pvInfoList[forwardMostMove].time =
17790                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17791         }
17792         flagged = CheckFlags();
17793     }
17794     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17795     CheckTimeControl();
17796
17797     if (flagged || !appData.clockMode) return;
17798
17799     switch (gameMode) {
17800       case MachinePlaysBlack:
17801       case MachinePlaysWhite:
17802       case BeginningOfGame:
17803         if (pausing) return;
17804         break;
17805
17806       case EditGame:
17807       case PlayFromGameFile:
17808       case IcsExamining:
17809         return;
17810
17811       default:
17812         break;
17813     }
17814
17815     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17816         if(WhiteOnMove(forwardMostMove))
17817              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17818         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17819     }
17820
17821     tickStartTM = now;
17822     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17823       whiteTimeRemaining : blackTimeRemaining);
17824     StartClockTimer(intendedTickLength);
17825 }
17826
17827
17828 /* Stop both clocks */
17829 void
17830 StopClocks ()
17831 {
17832     long lastTickLength;
17833     TimeMark now;
17834
17835     if (!StopClockTimer()) return;
17836     if (!appData.clockMode) return;
17837
17838     GetTimeMark(&now);
17839
17840     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17841     if (WhiteOnMove(forwardMostMove)) {
17842         if(whiteNPS >= 0) lastTickLength = 0;
17843         whiteTimeRemaining -= lastTickLength;
17844         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17845     } else {
17846         if(blackNPS >= 0) lastTickLength = 0;
17847         blackTimeRemaining -= lastTickLength;
17848         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17849     }
17850     CheckFlags();
17851 }
17852
17853 /* Start clock of player on move.  Time may have been reset, so
17854    if clock is already running, stop and restart it. */
17855 void
17856 StartClocks ()
17857 {
17858     (void) StopClockTimer(); /* in case it was running already */
17859     DisplayBothClocks();
17860     if (CheckFlags()) return;
17861
17862     if (!appData.clockMode) return;
17863     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17864
17865     GetTimeMark(&tickStartTM);
17866     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17867       whiteTimeRemaining : blackTimeRemaining);
17868
17869    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17870     whiteNPS = blackNPS = -1;
17871     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17872        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17873         whiteNPS = first.nps;
17874     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17875        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17876         blackNPS = first.nps;
17877     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17878         whiteNPS = second.nps;
17879     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17880         blackNPS = second.nps;
17881     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17882
17883     StartClockTimer(intendedTickLength);
17884 }
17885
17886 char *
17887 TimeString (long ms)
17888 {
17889     long second, minute, hour, day;
17890     char *sign = "";
17891     static char buf[32];
17892
17893     if (ms > 0 && ms <= 9900) {
17894       /* convert milliseconds to tenths, rounding up */
17895       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17896
17897       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17898       return buf;
17899     }
17900
17901     /* convert milliseconds to seconds, rounding up */
17902     /* use floating point to avoid strangeness of integer division
17903        with negative dividends on many machines */
17904     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17905
17906     if (second < 0) {
17907         sign = "-";
17908         second = -second;
17909     }
17910
17911     day = second / (60 * 60 * 24);
17912     second = second % (60 * 60 * 24);
17913     hour = second / (60 * 60);
17914     second = second % (60 * 60);
17915     minute = second / 60;
17916     second = second % 60;
17917
17918     if (day > 0)
17919       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17920               sign, day, hour, minute, second);
17921     else if (hour > 0)
17922       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17923     else
17924       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17925
17926     return buf;
17927 }
17928
17929
17930 /*
17931  * This is necessary because some C libraries aren't ANSI C compliant yet.
17932  */
17933 char *
17934 StrStr (char *string, char *match)
17935 {
17936     int i, length;
17937
17938     length = strlen(match);
17939
17940     for (i = strlen(string) - length; i >= 0; i--, string++)
17941       if (!strncmp(match, string, length))
17942         return string;
17943
17944     return NULL;
17945 }
17946
17947 char *
17948 StrCaseStr (char *string, char *match)
17949 {
17950     int i, j, length;
17951
17952     length = strlen(match);
17953
17954     for (i = strlen(string) - length; i >= 0; i--, string++) {
17955         for (j = 0; j < length; j++) {
17956             if (ToLower(match[j]) != ToLower(string[j]))
17957               break;
17958         }
17959         if (j == length) return string;
17960     }
17961
17962     return NULL;
17963 }
17964
17965 #ifndef _amigados
17966 int
17967 StrCaseCmp (char *s1, char *s2)
17968 {
17969     char c1, c2;
17970
17971     for (;;) {
17972         c1 = ToLower(*s1++);
17973         c2 = ToLower(*s2++);
17974         if (c1 > c2) return 1;
17975         if (c1 < c2) return -1;
17976         if (c1 == NULLCHAR) return 0;
17977     }
17978 }
17979
17980
17981 int
17982 ToLower (int c)
17983 {
17984     return isupper(c) ? tolower(c) : c;
17985 }
17986
17987
17988 int
17989 ToUpper (int c)
17990 {
17991     return islower(c) ? toupper(c) : c;
17992 }
17993 #endif /* !_amigados    */
17994
17995 char *
17996 StrSave (char *s)
17997 {
17998   char *ret;
17999
18000   if ((ret = (char *) malloc(strlen(s) + 1)))
18001     {
18002       safeStrCpy(ret, s, strlen(s)+1);
18003     }
18004   return ret;
18005 }
18006
18007 char *
18008 StrSavePtr (char *s, char **savePtr)
18009 {
18010     if (*savePtr) {
18011         free(*savePtr);
18012     }
18013     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18014       safeStrCpy(*savePtr, s, strlen(s)+1);
18015     }
18016     return(*savePtr);
18017 }
18018
18019 char *
18020 PGNDate ()
18021 {
18022     time_t clock;
18023     struct tm *tm;
18024     char buf[MSG_SIZ];
18025
18026     clock = time((time_t *)NULL);
18027     tm = localtime(&clock);
18028     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18029             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18030     return StrSave(buf);
18031 }
18032
18033
18034 char *
18035 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18036 {
18037     int i, j, fromX, fromY, toX, toY;
18038     int whiteToPlay, haveRights = nrCastlingRights;
18039     char buf[MSG_SIZ];
18040     char *p, *q;
18041     int emptycount;
18042     ChessSquare piece;
18043
18044     whiteToPlay = (gameMode == EditPosition) ?
18045       !blackPlaysFirst : (move % 2 == 0);
18046     p = buf;
18047
18048     /* Piece placement data */
18049     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18050         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18051         emptycount = 0;
18052         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18053             if (boards[move][i][j] == EmptySquare) {
18054                 emptycount++;
18055             } else { ChessSquare piece = boards[move][i][j];
18056                 if (emptycount > 0) {
18057                     if(emptycount<10) /* [HGM] can be >= 10 */
18058                         *p++ = '0' + emptycount;
18059                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18060                     emptycount = 0;
18061                 }
18062                 if(PieceToChar(piece) == '+') {
18063                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18064                     *p++ = '+';
18065                     piece = (ChessSquare)(CHUDEMOTED(piece));
18066                 }
18067                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18068                 if(*p = PieceSuffix(piece)) p++;
18069                 if(p[-1] == '~') {
18070                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18071                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18072                     *p++ = '~';
18073                 }
18074             }
18075         }
18076         if (emptycount > 0) {
18077             if(emptycount<10) /* [HGM] can be >= 10 */
18078                 *p++ = '0' + emptycount;
18079             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18080             emptycount = 0;
18081         }
18082         *p++ = '/';
18083     }
18084     *(p - 1) = ' ';
18085
18086     /* [HGM] print Crazyhouse or Shogi holdings */
18087     if( gameInfo.holdingsWidth ) {
18088         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18089         q = p;
18090         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18091             piece = boards[move][i][BOARD_WIDTH-1];
18092             if( piece != EmptySquare )
18093               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18094                   *p++ = PieceToChar(piece);
18095         }
18096         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18097             piece = boards[move][BOARD_HEIGHT-i-1][0];
18098             if( piece != EmptySquare )
18099               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18100                   *p++ = PieceToChar(piece);
18101         }
18102
18103         if( q == p ) *p++ = '-';
18104         *p++ = ']';
18105         *p++ = ' ';
18106     }
18107
18108     /* Active color */
18109     *p++ = whiteToPlay ? 'w' : 'b';
18110     *p++ = ' ';
18111
18112   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18113     haveRights = 0; q = p;
18114     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18115       piece = boards[move][0][i];
18116       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18117         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18118       }
18119     }
18120     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18121       piece = boards[move][BOARD_HEIGHT-1][i];
18122       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18123         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18124       }
18125     }
18126     if(p == q) *p++ = '-';
18127     *p++ = ' ';
18128   }
18129
18130   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18131     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18132   } else {
18133   if(haveRights) {
18134      int handW=0, handB=0;
18135      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18136         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18137         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18138      }
18139      q = p;
18140      if(appData.fischerCastling) {
18141         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18142            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18143                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18144         } else {
18145        /* [HGM] write directly from rights */
18146            if(boards[move][CASTLING][2] != NoRights &&
18147               boards[move][CASTLING][0] != NoRights   )
18148                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18149            if(boards[move][CASTLING][2] != NoRights &&
18150               boards[move][CASTLING][1] != NoRights   )
18151                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18152         }
18153         if(handB) {
18154            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18155                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18156         } else {
18157            if(boards[move][CASTLING][5] != NoRights &&
18158               boards[move][CASTLING][3] != NoRights   )
18159                 *p++ = boards[move][CASTLING][3] + AAA;
18160            if(boards[move][CASTLING][5] != NoRights &&
18161               boards[move][CASTLING][4] != NoRights   )
18162                 *p++ = boards[move][CASTLING][4] + AAA;
18163         }
18164      } else {
18165
18166         /* [HGM] write true castling rights */
18167         if( nrCastlingRights == 6 ) {
18168             int q, k=0;
18169             if(boards[move][CASTLING][0] != NoRights &&
18170                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18171             q = (boards[move][CASTLING][1] != NoRights &&
18172                  boards[move][CASTLING][2] != NoRights  );
18173             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18174                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18175                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18176                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18177             }
18178             if(q) *p++ = 'Q';
18179             k = 0;
18180             if(boards[move][CASTLING][3] != NoRights &&
18181                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18182             q = (boards[move][CASTLING][4] != NoRights &&
18183                  boards[move][CASTLING][5] != NoRights  );
18184             if(handB) {
18185                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18186                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18187                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18188             }
18189             if(q) *p++ = 'q';
18190         }
18191      }
18192      if (q == p) *p++ = '-'; /* No castling rights */
18193      *p++ = ' ';
18194   }
18195
18196   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18197      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18198      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18199     /* En passant target square */
18200     if (move > backwardMostMove) {
18201         fromX = moveList[move - 1][0] - AAA;
18202         fromY = moveList[move - 1][1] - ONE;
18203         toX = moveList[move - 1][2] - AAA;
18204         toY = moveList[move - 1][3] - ONE;
18205         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18206             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18207             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18208             fromX == toX) {
18209             /* 2-square pawn move just happened */
18210             *p++ = toX + AAA;
18211             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18212         } else {
18213             *p++ = '-';
18214         }
18215     } else if(move == backwardMostMove) {
18216         // [HGM] perhaps we should always do it like this, and forget the above?
18217         if((signed char)boards[move][EP_STATUS] >= 0) {
18218             *p++ = boards[move][EP_STATUS] + AAA;
18219             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18220         } else {
18221             *p++ = '-';
18222         }
18223     } else {
18224         *p++ = '-';
18225     }
18226     *p++ = ' ';
18227   }
18228   }
18229
18230     if(moveCounts)
18231     {   int i = 0, j=move;
18232
18233         /* [HGM] find reversible plies */
18234         if (appData.debugMode) { int k;
18235             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18236             for(k=backwardMostMove; k<=forwardMostMove; k++)
18237                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18238
18239         }
18240
18241         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18242         if( j == backwardMostMove ) i += initialRulePlies;
18243         sprintf(p, "%d ", i);
18244         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18245
18246         /* Fullmove number */
18247         sprintf(p, "%d", (move / 2) + 1);
18248     } else *--p = NULLCHAR;
18249
18250     return StrSave(buf);
18251 }
18252
18253 Boolean
18254 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18255 {
18256     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18257     char *p, c;
18258     int emptycount, virgin[BOARD_FILES];
18259     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18260
18261     p = fen;
18262
18263     /* Piece placement data */
18264     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18265         j = 0;
18266         for (;;) {
18267             if (*p == '/' || *p == ' ' || *p == '[' ) {
18268                 if(j > w) w = j;
18269                 emptycount = gameInfo.boardWidth - j;
18270                 while (emptycount--)
18271                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18272                 if (*p == '/') p++;
18273                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18274                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18275                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18276                     }
18277                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18278                 }
18279                 break;
18280 #if(BOARD_FILES >= 10)*0
18281             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18282                 p++; emptycount=10;
18283                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18284                 while (emptycount--)
18285                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18286 #endif
18287             } else if (*p == '*') {
18288                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18289             } else if (isdigit(*p)) {
18290                 emptycount = *p++ - '0';
18291                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18292                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18293                 while (emptycount--)
18294                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18295             } else if (*p == '<') {
18296                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18297                 else if (i != 0 || !shuffle) return FALSE;
18298                 p++;
18299             } else if (shuffle && *p == '>') {
18300                 p++; // for now ignore closing shuffle range, and assume rank-end
18301             } else if (*p == '?') {
18302                 if (j >= gameInfo.boardWidth) return FALSE;
18303                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18304                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18305             } else if (*p == '+' || isalpha(*p)) {
18306                 char *q, *s = SUFFIXES;
18307                 if (j >= gameInfo.boardWidth) return FALSE;
18308                 if(*p=='+') {
18309                     char c = *++p;
18310                     if(q = strchr(s, p[1])) p++;
18311                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18312                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18313                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18314                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18315                 } else {
18316                     char c = *p++;
18317                     if(q = strchr(s, *p)) p++;
18318                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18319                 }
18320
18321                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18322                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18323                     piece = (ChessSquare) (PROMOTED(piece));
18324                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18325                     p++;
18326                 }
18327                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18328                 if(piece == king) wKingRank = i;
18329                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18330             } else {
18331                 return FALSE;
18332             }
18333         }
18334     }
18335     while (*p == '/' || *p == ' ') p++;
18336
18337     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18338
18339     /* [HGM] by default clear Crazyhouse holdings, if present */
18340     if(gameInfo.holdingsWidth) {
18341        for(i=0; i<BOARD_HEIGHT; i++) {
18342            board[i][0]             = EmptySquare; /* black holdings */
18343            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18344            board[i][1]             = (ChessSquare) 0; /* black counts */
18345            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18346        }
18347     }
18348
18349     /* [HGM] look for Crazyhouse holdings here */
18350     while(*p==' ') p++;
18351     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18352         int swap=0, wcnt=0, bcnt=0;
18353         if(*p == '[') p++;
18354         if(*p == '<') swap++, p++;
18355         if(*p == '-' ) p++; /* empty holdings */ else {
18356             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18357             /* if we would allow FEN reading to set board size, we would   */
18358             /* have to add holdings and shift the board read so far here   */
18359             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18360                 p++;
18361                 if((int) piece >= (int) BlackPawn ) {
18362                     i = (int)piece - (int)BlackPawn;
18363                     i = PieceToNumber((ChessSquare)i);
18364                     if( i >= gameInfo.holdingsSize ) return FALSE;
18365                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18366                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18367                     bcnt++;
18368                 } else {
18369                     i = (int)piece - (int)WhitePawn;
18370                     i = PieceToNumber((ChessSquare)i);
18371                     if( i >= gameInfo.holdingsSize ) return FALSE;
18372                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18373                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18374                     wcnt++;
18375                 }
18376             }
18377             if(subst) { // substitute back-rank question marks by holdings pieces
18378                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18379                     int k, m, n = bcnt + 1;
18380                     if(board[0][j] == ClearBoard) {
18381                         if(!wcnt) return FALSE;
18382                         n = rand() % wcnt;
18383                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18384                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18385                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18386                             break;
18387                         }
18388                     }
18389                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18390                         if(!bcnt) return FALSE;
18391                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18392                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18393                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18394                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18395                             break;
18396                         }
18397                     }
18398                 }
18399                 subst = 0;
18400             }
18401         }
18402         if(*p == ']') p++;
18403     }
18404
18405     if(subst) return FALSE; // substitution requested, but no holdings
18406
18407     while(*p == ' ') p++;
18408
18409     /* Active color */
18410     c = *p++;
18411     if(appData.colorNickNames) {
18412       if( c == appData.colorNickNames[0] ) c = 'w'; else
18413       if( c == appData.colorNickNames[1] ) c = 'b';
18414     }
18415     switch (c) {
18416       case 'w':
18417         *blackPlaysFirst = FALSE;
18418         break;
18419       case 'b':
18420         *blackPlaysFirst = TRUE;
18421         break;
18422       default:
18423         return FALSE;
18424     }
18425
18426     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18427     /* return the extra info in global variiables             */
18428
18429     while(*p==' ') p++;
18430
18431     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18432         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18433         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18434     }
18435
18436     /* set defaults in case FEN is incomplete */
18437     board[EP_STATUS] = EP_UNKNOWN;
18438     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18439     for(i=0; i<nrCastlingRights; i++ ) {
18440         board[CASTLING][i] =
18441             appData.fischerCastling ? NoRights : initialRights[i];
18442     }   /* assume possible unless obviously impossible */
18443     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18444     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18445     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18446                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18447     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18448     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18449     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18450                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18451     FENrulePlies = 0;
18452
18453     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18454       char *q = p;
18455       int w=0, b=0;
18456       while(isalpha(*p)) {
18457         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18458         if(islower(*p)) b |= 1 << (*p++ - 'a');
18459       }
18460       if(*p == '-') p++;
18461       if(p != q) {
18462         board[TOUCHED_W] = ~w;
18463         board[TOUCHED_B] = ~b;
18464         while(*p == ' ') p++;
18465       }
18466     } else
18467
18468     if(nrCastlingRights) {
18469       int fischer = 0;
18470       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18471       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18472           /* castling indicator present, so default becomes no castlings */
18473           for(i=0; i<nrCastlingRights; i++ ) {
18474                  board[CASTLING][i] = NoRights;
18475           }
18476       }
18477       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18478              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18479              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18480              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18481         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18482
18483         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18484             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18485             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18486         }
18487         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18488             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18489         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18490                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18491         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18492                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18493         switch(c) {
18494           case'K':
18495               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18496               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18497               board[CASTLING][2] = whiteKingFile;
18498               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18499               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18500               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18501               break;
18502           case'Q':
18503               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18504               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18505               board[CASTLING][2] = whiteKingFile;
18506               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18507               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18508               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18509               break;
18510           case'k':
18511               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18512               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18513               board[CASTLING][5] = blackKingFile;
18514               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18515               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18516               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18517               break;
18518           case'q':
18519               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18520               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18521               board[CASTLING][5] = blackKingFile;
18522               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18523               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18524               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18525           case '-':
18526               break;
18527           default: /* FRC castlings */
18528               if(c >= 'a') { /* black rights */
18529                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18530                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18531                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18532                   if(i == BOARD_RGHT) break;
18533                   board[CASTLING][5] = i;
18534                   c -= AAA;
18535                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18536                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18537                   if(c > i)
18538                       board[CASTLING][3] = c;
18539                   else
18540                       board[CASTLING][4] = c;
18541               } else { /* white rights */
18542                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18543                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18544                     if(board[0][i] == WhiteKing) break;
18545                   if(i == BOARD_RGHT) break;
18546                   board[CASTLING][2] = i;
18547                   c -= AAA - 'a' + 'A';
18548                   if(board[0][c] >= WhiteKing) break;
18549                   if(c > i)
18550                       board[CASTLING][0] = c;
18551                   else
18552                       board[CASTLING][1] = c;
18553               }
18554         }
18555       }
18556       for(i=0; i<nrCastlingRights; i++)
18557         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18558       if(gameInfo.variant == VariantSChess)
18559         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18560       if(fischer && shuffle) appData.fischerCastling = TRUE;
18561     if (appData.debugMode) {
18562         fprintf(debugFP, "FEN castling rights:");
18563         for(i=0; i<nrCastlingRights; i++)
18564         fprintf(debugFP, " %d", board[CASTLING][i]);
18565         fprintf(debugFP, "\n");
18566     }
18567
18568       while(*p==' ') p++;
18569     }
18570
18571     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18572
18573     /* read e.p. field in games that know e.p. capture */
18574     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18575        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18576        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18577       if(*p=='-') {
18578         p++; board[EP_STATUS] = EP_NONE;
18579       } else {
18580          char c = *p++ - AAA;
18581
18582          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18583          if(*p >= '0' && *p <='9') p++;
18584          board[EP_STATUS] = c;
18585       }
18586     }
18587
18588
18589     if(sscanf(p, "%d", &i) == 1) {
18590         FENrulePlies = i; /* 50-move ply counter */
18591         /* (The move number is still ignored)    */
18592     }
18593
18594     return TRUE;
18595 }
18596
18597 void
18598 EditPositionPasteFEN (char *fen)
18599 {
18600   if (fen != NULL) {
18601     Board initial_position;
18602
18603     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18604       DisplayError(_("Bad FEN position in clipboard"), 0);
18605       return ;
18606     } else {
18607       int savedBlackPlaysFirst = blackPlaysFirst;
18608       EditPositionEvent();
18609       blackPlaysFirst = savedBlackPlaysFirst;
18610       CopyBoard(boards[0], initial_position);
18611       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18612       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18613       DisplayBothClocks();
18614       DrawPosition(FALSE, boards[currentMove]);
18615     }
18616   }
18617 }
18618
18619 static char cseq[12] = "\\   ";
18620
18621 Boolean
18622 set_cont_sequence (char *new_seq)
18623 {
18624     int len;
18625     Boolean ret;
18626
18627     // handle bad attempts to set the sequence
18628         if (!new_seq)
18629                 return 0; // acceptable error - no debug
18630
18631     len = strlen(new_seq);
18632     ret = (len > 0) && (len < sizeof(cseq));
18633     if (ret)
18634       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18635     else if (appData.debugMode)
18636       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18637     return ret;
18638 }
18639
18640 /*
18641     reformat a source message so words don't cross the width boundary.  internal
18642     newlines are not removed.  returns the wrapped size (no null character unless
18643     included in source message).  If dest is NULL, only calculate the size required
18644     for the dest buffer.  lp argument indicats line position upon entry, and it's
18645     passed back upon exit.
18646 */
18647 int
18648 wrap (char *dest, char *src, int count, int width, int *lp)
18649 {
18650     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18651
18652     cseq_len = strlen(cseq);
18653     old_line = line = *lp;
18654     ansi = len = clen = 0;
18655
18656     for (i=0; i < count; i++)
18657     {
18658         if (src[i] == '\033')
18659             ansi = 1;
18660
18661         // if we hit the width, back up
18662         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18663         {
18664             // store i & len in case the word is too long
18665             old_i = i, old_len = len;
18666
18667             // find the end of the last word
18668             while (i && src[i] != ' ' && src[i] != '\n')
18669             {
18670                 i--;
18671                 len--;
18672             }
18673
18674             // word too long?  restore i & len before splitting it
18675             if ((old_i-i+clen) >= width)
18676             {
18677                 i = old_i;
18678                 len = old_len;
18679             }
18680
18681             // extra space?
18682             if (i && src[i-1] == ' ')
18683                 len--;
18684
18685             if (src[i] != ' ' && src[i] != '\n')
18686             {
18687                 i--;
18688                 if (len)
18689                     len--;
18690             }
18691
18692             // now append the newline and continuation sequence
18693             if (dest)
18694                 dest[len] = '\n';
18695             len++;
18696             if (dest)
18697                 strncpy(dest+len, cseq, cseq_len);
18698             len += cseq_len;
18699             line = cseq_len;
18700             clen = cseq_len;
18701             continue;
18702         }
18703
18704         if (dest)
18705             dest[len] = src[i];
18706         len++;
18707         if (!ansi)
18708             line++;
18709         if (src[i] == '\n')
18710             line = 0;
18711         if (src[i] == 'm')
18712             ansi = 0;
18713     }
18714     if (dest && appData.debugMode)
18715     {
18716         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18717             count, width, line, len, *lp);
18718         show_bytes(debugFP, src, count);
18719         fprintf(debugFP, "\ndest: ");
18720         show_bytes(debugFP, dest, len);
18721         fprintf(debugFP, "\n");
18722     }
18723     *lp = dest ? line : old_line;
18724
18725     return len;
18726 }
18727
18728 // [HGM] vari: routines for shelving variations
18729 Boolean modeRestore = FALSE;
18730
18731 void
18732 PushInner (int firstMove, int lastMove)
18733 {
18734         int i, j, nrMoves = lastMove - firstMove;
18735
18736         // push current tail of game on stack
18737         savedResult[storedGames] = gameInfo.result;
18738         savedDetails[storedGames] = gameInfo.resultDetails;
18739         gameInfo.resultDetails = NULL;
18740         savedFirst[storedGames] = firstMove;
18741         savedLast [storedGames] = lastMove;
18742         savedFramePtr[storedGames] = framePtr;
18743         framePtr -= nrMoves; // reserve space for the boards
18744         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18745             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18746             for(j=0; j<MOVE_LEN; j++)
18747                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18748             for(j=0; j<2*MOVE_LEN; j++)
18749                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18750             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18751             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18752             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18753             pvInfoList[firstMove+i-1].depth = 0;
18754             commentList[framePtr+i] = commentList[firstMove+i];
18755             commentList[firstMove+i] = NULL;
18756         }
18757
18758         storedGames++;
18759         forwardMostMove = firstMove; // truncate game so we can start variation
18760 }
18761
18762 void
18763 PushTail (int firstMove, int lastMove)
18764 {
18765         if(appData.icsActive) { // only in local mode
18766                 forwardMostMove = currentMove; // mimic old ICS behavior
18767                 return;
18768         }
18769         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18770
18771         PushInner(firstMove, lastMove);
18772         if(storedGames == 1) GreyRevert(FALSE);
18773         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18774 }
18775
18776 void
18777 PopInner (Boolean annotate)
18778 {
18779         int i, j, nrMoves;
18780         char buf[8000], moveBuf[20];
18781
18782         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18783         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18784         nrMoves = savedLast[storedGames] - currentMove;
18785         if(annotate) {
18786                 int cnt = 10;
18787                 if(!WhiteOnMove(currentMove))
18788                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18789                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18790                 for(i=currentMove; i<forwardMostMove; i++) {
18791                         if(WhiteOnMove(i))
18792                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18793                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18794                         strcat(buf, moveBuf);
18795                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18796                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18797                 }
18798                 strcat(buf, ")");
18799         }
18800         for(i=1; i<=nrMoves; i++) { // copy last variation back
18801             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18802             for(j=0; j<MOVE_LEN; j++)
18803                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18804             for(j=0; j<2*MOVE_LEN; j++)
18805                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18806             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18807             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18808             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18809             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18810             commentList[currentMove+i] = commentList[framePtr+i];
18811             commentList[framePtr+i] = NULL;
18812         }
18813         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18814         framePtr = savedFramePtr[storedGames];
18815         gameInfo.result = savedResult[storedGames];
18816         if(gameInfo.resultDetails != NULL) {
18817             free(gameInfo.resultDetails);
18818       }
18819         gameInfo.resultDetails = savedDetails[storedGames];
18820         forwardMostMove = currentMove + nrMoves;
18821 }
18822
18823 Boolean
18824 PopTail (Boolean annotate)
18825 {
18826         if(appData.icsActive) return FALSE; // only in local mode
18827         if(!storedGames) return FALSE; // sanity
18828         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18829
18830         PopInner(annotate);
18831         if(currentMove < forwardMostMove) ForwardEvent(); else
18832         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18833
18834         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18835         return TRUE;
18836 }
18837
18838 void
18839 CleanupTail ()
18840 {       // remove all shelved variations
18841         int i;
18842         for(i=0; i<storedGames; i++) {
18843             if(savedDetails[i])
18844                 free(savedDetails[i]);
18845             savedDetails[i] = NULL;
18846         }
18847         for(i=framePtr; i<MAX_MOVES; i++) {
18848                 if(commentList[i]) free(commentList[i]);
18849                 commentList[i] = NULL;
18850         }
18851         framePtr = MAX_MOVES-1;
18852         storedGames = 0;
18853 }
18854
18855 void
18856 LoadVariation (int index, char *text)
18857 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18858         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18859         int level = 0, move;
18860
18861         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18862         // first find outermost bracketing variation
18863         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18864             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18865                 if(*p == '{') wait = '}'; else
18866                 if(*p == '[') wait = ']'; else
18867                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18868                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18869             }
18870             if(*p == wait) wait = NULLCHAR; // closing ]} found
18871             p++;
18872         }
18873         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18874         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18875         end[1] = NULLCHAR; // clip off comment beyond variation
18876         ToNrEvent(currentMove-1);
18877         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18878         // kludge: use ParsePV() to append variation to game
18879         move = currentMove;
18880         ParsePV(start, TRUE, TRUE);
18881         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18882         ClearPremoveHighlights();
18883         CommentPopDown();
18884         ToNrEvent(currentMove+1);
18885 }
18886
18887 void
18888 LoadTheme ()
18889 {
18890     char *p, *q, buf[MSG_SIZ];
18891     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18892         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18893         ParseArgsFromString(buf);
18894         ActivateTheme(TRUE); // also redo colors
18895         return;
18896     }
18897     p = nickName;
18898     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18899     {
18900         int len;
18901         q = appData.themeNames;
18902         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18903       if(appData.useBitmaps) {
18904         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18905                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18906                 appData.liteBackTextureMode,
18907                 appData.darkBackTextureMode );
18908       } else {
18909         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18910                 Col2Text(2),   // lightSquareColor
18911                 Col2Text(3) ); // darkSquareColor
18912       }
18913       if(appData.useBorder) {
18914         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18915                 appData.border);
18916       } else {
18917         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18918       }
18919       if(appData.useFont) {
18920         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18921                 appData.renderPiecesWithFont,
18922                 appData.fontToPieceTable,
18923                 Col2Text(9),    // appData.fontBackColorWhite
18924                 Col2Text(10) ); // appData.fontForeColorBlack
18925       } else {
18926         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18927                 appData.pieceDirectory);
18928         if(!appData.pieceDirectory[0])
18929           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18930                 Col2Text(0),   // whitePieceColor
18931                 Col2Text(1) ); // blackPieceColor
18932       }
18933       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18934                 Col2Text(4),   // highlightSquareColor
18935                 Col2Text(5) ); // premoveHighlightColor
18936         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18937         if(insert != q) insert[-1] = NULLCHAR;
18938         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18939         if(q)   free(q);
18940     }
18941     ActivateTheme(FALSE);
18942 }