Always assume FEN in variant-fairy PGN game is initial position
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58     int flock(int f, int code);
59 #   define LOCK_EX 2
60 #   define SLASH '\\'
61
62 #   ifdef ARC_64BIT
63 #       define EGBB_NAME "egbbdll64.dll"
64 #   else
65 #       define EGBB_NAME "egbbdll.dll"
66 #   endif
67
68 #else
69
70 #   include <sys/file.h>
71 #   define SLASH '/'
72
73 #   include <dlfcn.h>
74 #   ifdef ARC_64BIT
75 #       define EGBB_NAME "egbbso64.so"
76 #   else
77 #       define EGBB_NAME "egbbso.so"
78 #   endif
79     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
80 #   define CDECL
81 #   define HMODULE void *
82 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 #   define GetProcAddress dlsym
84
85 #endif
86
87 #include "config.h"
88
89 #include <assert.h>
90 #include <stdio.h>
91 #include <ctype.h>
92 #include <errno.h>
93 #include <sys/types.h>
94 #include <sys/stat.h>
95 #include <math.h>
96 #include <ctype.h>
97
98 #if STDC_HEADERS
99 # include <stdlib.h>
100 # include <string.h>
101 # include <stdarg.h>
102 #else /* not STDC_HEADERS */
103 # if HAVE_STRING_H
104 #  include <string.h>
105 # else /* not HAVE_STRING_H */
106 #  include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
109
110 #if HAVE_SYS_FCNTL_H
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
113 # if HAVE_FCNTL_H
114 #  include <fcntl.h>
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
117
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
120 # include <time.h>
121 #else
122 # if HAVE_SYS_TIME_H
123 #  include <sys/time.h>
124 # else
125 #  include <time.h>
126 # endif
127 #endif
128
129 #if defined(_amigados) && !defined(__GNUC__)
130 struct timezone {
131     int tz_minuteswest;
132     int tz_dsttime;
133 };
134 extern int gettimeofday(struct timeval *, struct timezone *);
135 #endif
136
137 #if HAVE_UNISTD_H
138 # include <unistd.h>
139 #endif
140
141 #include "common.h"
142 #include "frontend.h"
143 #include "backend.h"
144 #include "parser.h"
145 #include "moves.h"
146 #if ZIPPY
147 # include "zippy.h"
148 #endif
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
152 #include "gettext.h"
153
154 #ifdef ENABLE_NLS
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
158 #else
159 # ifdef WIN32
160 #   define _(s) T_(s)
161 #   define N_(s) s
162 # else
163 #   define _(s) (s)
164 #   define N_(s) s
165 #   define T_(s) s
166 # endif
167 #endif
168
169
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172                          char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174                       char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187                    /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void SendToProgram P((char *message, ChessProgramState *cps));
196 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
197 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
198                            char *buf, int count, int error));
199 void SendTimeControl P((ChessProgramState *cps,
200                         int mps, long tc, int inc, int sd, int st));
201 char *TimeControlTagValue P((void));
202 void Attention P((ChessProgramState *cps));
203 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
204 int ResurrectChessProgram P((void));
205 void DisplayComment P((int moveNumber, char *text));
206 void DisplayMove P((int moveNumber));
207
208 void ParseGameHistory P((char *game));
209 void ParseBoard12 P((char *string));
210 void KeepAlive P((void));
211 void StartClocks P((void));
212 void SwitchClocks P((int nr));
213 void StopClocks P((void));
214 void ResetClocks P((void));
215 char *PGNDate P((void));
216 void SetGameInfo P((void));
217 int RegisterMove P((void));
218 void MakeRegisteredMove P((void));
219 void TruncateGame P((void));
220 int looking_at P((char *, int *, char *));
221 void CopyPlayerNameIntoFileName P((char **, char *));
222 char *SavePart P((char *));
223 int SaveGameOldStyle P((FILE *));
224 int SaveGamePGN P((FILE *));
225 int CheckFlags P((void));
226 long NextTickLength P((long));
227 void CheckTimeControl P((void));
228 void show_bytes P((FILE *, char *, int));
229 int string_to_rating P((char *str));
230 void ParseFeatures P((char* args, ChessProgramState *cps));
231 void InitBackEnd3 P((void));
232 void FeatureDone P((ChessProgramState* cps, int val));
233 void InitChessProgram P((ChessProgramState *cps, int setup));
234 void OutputKibitz(int window, char *text);
235 int PerpetualChase(int first, int last);
236 int EngineOutputIsUp();
237 void InitDrawingSizes(int x, int y);
238 void NextMatchGame P((void));
239 int NextTourneyGame P((int nr, int *swap));
240 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
241 FILE *WriteTourneyFile P((char *results, FILE *f));
242 void DisplayTwoMachinesTitle P(());
243 static void ExcludeClick P((int index));
244 void ToggleSecond P((void));
245 void PauseEngine P((ChessProgramState *cps));
246 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
247
248 #ifdef WIN32
249        extern void ConsoleCreate();
250 #endif
251
252 ChessProgramState *WhitePlayer();
253 int VerifyDisplayMode P(());
254
255 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
256 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
257 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
258 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
259 void ics_update_width P((int new_width));
260 extern char installDir[MSG_SIZ];
261 VariantClass startVariant; /* [HGM] nicks: initial variant */
262 Boolean abortMatch;
263
264 extern int tinyLayout, smallLayout;
265 ChessProgramStats programStats;
266 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
267 int endPV = -1;
268 static int exiting = 0; /* [HGM] moved to top */
269 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
270 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
271 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
272 int partnerHighlight[2];
273 Boolean partnerBoardValid = 0;
274 char partnerStatus[MSG_SIZ];
275 Boolean partnerUp;
276 Boolean originalFlip;
277 Boolean twoBoards = 0;
278 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
279 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
280 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
281 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
282 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
283 int opponentKibitzes;
284 int lastSavedGame; /* [HGM] save: ID of game */
285 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
286 extern int chatCount;
287 int chattingPartner;
288 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
289 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
290 char lastMsg[MSG_SIZ];
291 char lastTalker[MSG_SIZ];
292 ChessSquare pieceSweep = EmptySquare;
293 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
294 int promoDefaultAltered;
295 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
296 static int initPing = -1;
297 int border;       /* [HGM] width of board rim, needed to size seek graph  */
298 char bestMove[MSG_SIZ];
299 int solvingTime, totalTime;
300
301 /* States for ics_getting_history */
302 #define H_FALSE 0
303 #define H_REQUESTED 1
304 #define H_GOT_REQ_HEADER 2
305 #define H_GOT_UNREQ_HEADER 3
306 #define H_GETTING_MOVES 4
307 #define H_GOT_UNWANTED_HEADER 5
308
309 /* whosays values for GameEnds */
310 #define GE_ICS 0
311 #define GE_ENGINE 1
312 #define GE_PLAYER 2
313 #define GE_FILE 3
314 #define GE_XBOARD 4
315 #define GE_ENGINE1 5
316 #define GE_ENGINE2 6
317
318 /* Maximum number of games in a cmail message */
319 #define CMAIL_MAX_GAMES 20
320
321 /* Different types of move when calling RegisterMove */
322 #define CMAIL_MOVE   0
323 #define CMAIL_RESIGN 1
324 #define CMAIL_DRAW   2
325 #define CMAIL_ACCEPT 3
326
327 /* Different types of result to remember for each game */
328 #define CMAIL_NOT_RESULT 0
329 #define CMAIL_OLD_RESULT 1
330 #define CMAIL_NEW_RESULT 2
331
332 /* Telnet protocol constants */
333 #define TN_WILL 0373
334 #define TN_WONT 0374
335 #define TN_DO   0375
336 #define TN_DONT 0376
337 #define TN_IAC  0377
338 #define TN_ECHO 0001
339 #define TN_SGA  0003
340 #define TN_PORT 23
341
342 char*
343 safeStrCpy (char *dst, const char *src, size_t count)
344 { // [HGM] made safe
345   int i;
346   assert( dst != NULL );
347   assert( src != NULL );
348   assert( count > 0 );
349
350   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
351   if(  i == count && dst[count-1] != NULLCHAR)
352     {
353       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
354       if(appData.debugMode)
355         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
356     }
357
358   return dst;
359 }
360
361 /* Some compiler can't cast u64 to double
362  * This function do the job for us:
363
364  * We use the highest bit for cast, this only
365  * works if the highest bit is not
366  * in use (This should not happen)
367  *
368  * We used this for all compiler
369  */
370 double
371 u64ToDouble (u64 value)
372 {
373   double r;
374   u64 tmp = value & u64Const(0x7fffffffffffffff);
375   r = (double)(s64)tmp;
376   if (value & u64Const(0x8000000000000000))
377        r +=  9.2233720368547758080e18; /* 2^63 */
378  return r;
379 }
380
381 /* Fake up flags for now, as we aren't keeping track of castling
382    availability yet. [HGM] Change of logic: the flag now only
383    indicates the type of castlings allowed by the rule of the game.
384    The actual rights themselves are maintained in the array
385    castlingRights, as part of the game history, and are not probed
386    by this function.
387  */
388 int
389 PosFlags (index)
390 {
391   int flags = F_ALL_CASTLE_OK;
392   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
393   switch (gameInfo.variant) {
394   case VariantSuicide:
395     flags &= ~F_ALL_CASTLE_OK;
396   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
397     flags |= F_IGNORE_CHECK;
398   case VariantLosers:
399     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
400     break;
401   case VariantAtomic:
402     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
403     break;
404   case VariantKriegspiel:
405     flags |= F_KRIEGSPIEL_CAPTURE;
406     break;
407   case VariantCapaRandom:
408   case VariantFischeRandom:
409     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
410   case VariantNoCastle:
411   case VariantShatranj:
412   case VariantCourier:
413   case VariantMakruk:
414   case VariantASEAN:
415   case VariantGrand:
416     flags &= ~F_ALL_CASTLE_OK;
417     break;
418   case VariantChu:
419   case VariantChuChess:
420   case VariantLion:
421     flags |= F_NULL_MOVE;
422     break;
423   default:
424     break;
425   }
426   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
427   return flags;
428 }
429
430 FILE *gameFileFP, *debugFP, *serverFP;
431 char *currentDebugFile; // [HGM] debug split: to remember name
432
433 /*
434     [AS] Note: sometimes, the sscanf() function is used to parse the input
435     into a fixed-size buffer. Because of this, we must be prepared to
436     receive strings as long as the size of the input buffer, which is currently
437     set to 4K for Windows and 8K for the rest.
438     So, we must either allocate sufficiently large buffers here, or
439     reduce the size of the input buffer in the input reading part.
440 */
441
442 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
443 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
444 char thinkOutput1[MSG_SIZ*10];
445 char promoRestrict[MSG_SIZ];
446
447 ChessProgramState first, second, pairing;
448
449 /* premove variables */
450 int premoveToX = 0;
451 int premoveToY = 0;
452 int premoveFromX = 0;
453 int premoveFromY = 0;
454 int premovePromoChar = 0;
455 int gotPremove = 0;
456 Boolean alarmSounded;
457 /* end premove variables */
458
459 char *ics_prefix = "$";
460 enum ICS_TYPE ics_type = ICS_GENERIC;
461
462 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
463 int pauseExamForwardMostMove = 0;
464 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
465 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
466 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
467 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
468 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
469 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
470 int whiteFlag = FALSE, blackFlag = FALSE;
471 int userOfferedDraw = FALSE;
472 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
473 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
474 int cmailMoveType[CMAIL_MAX_GAMES];
475 long ics_clock_paused = 0;
476 ProcRef icsPR = NoProc, cmailPR = NoProc;
477 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
478 GameMode gameMode = BeginningOfGame;
479 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
480 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
481 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
482 int hiddenThinkOutputState = 0; /* [AS] */
483 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
484 int adjudicateLossPlies = 6;
485 char white_holding[64], black_holding[64];
486 TimeMark lastNodeCountTime;
487 long lastNodeCount=0;
488 int shiftKey, controlKey; // [HGM] set by mouse handler
489
490 int have_sent_ICS_logon = 0;
491 int movesPerSession;
492 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
493 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
494 Boolean adjustedClock;
495 long timeControl_2; /* [AS] Allow separate time controls */
496 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
497 long timeRemaining[2][MAX_MOVES];
498 int matchGame = 0, nextGame = 0, roundNr = 0;
499 Boolean waitingForGame = FALSE, startingEngine = FALSE;
500 TimeMark programStartTime, pauseStart;
501 char ics_handle[MSG_SIZ];
502 int have_set_title = 0;
503
504 /* animateTraining preserves the state of appData.animate
505  * when Training mode is activated. This allows the
506  * response to be animated when appData.animate == TRUE and
507  * appData.animateDragging == TRUE.
508  */
509 Boolean animateTraining;
510
511 GameInfo gameInfo;
512
513 AppData appData;
514
515 Board boards[MAX_MOVES];
516 /* [HGM] Following 7 needed for accurate legality tests: */
517 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
518 unsigned char initialRights[BOARD_FILES];
519 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
520 int   initialRulePlies, FENrulePlies;
521 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
522 int loadFlag = 0;
523 Boolean shuffleOpenings;
524 int mute; // mute all sounds
525
526 // [HGM] vari: next 12 to save and restore variations
527 #define MAX_VARIATIONS 10
528 int framePtr = MAX_MOVES-1; // points to free stack entry
529 int storedGames = 0;
530 int savedFirst[MAX_VARIATIONS];
531 int savedLast[MAX_VARIATIONS];
532 int savedFramePtr[MAX_VARIATIONS];
533 char *savedDetails[MAX_VARIATIONS];
534 ChessMove savedResult[MAX_VARIATIONS];
535
536 void PushTail P((int firstMove, int lastMove));
537 Boolean PopTail P((Boolean annotate));
538 void PushInner P((int firstMove, int lastMove));
539 void PopInner P((Boolean annotate));
540 void CleanupTail P((void));
541
542 ChessSquare  FIDEArray[2][BOARD_FILES] = {
543     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
546         BlackKing, BlackBishop, BlackKnight, BlackRook }
547 };
548
549 ChessSquare twoKingsArray[2][BOARD_FILES] = {
550     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
551         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
553         BlackKing, BlackKing, BlackKnight, BlackRook }
554 };
555
556 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
558         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
559     { BlackRook, BlackMan, BlackBishop, BlackQueen,
560         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
561 };
562
563 ChessSquare SpartanArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
565         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
566     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
567         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
568 };
569
570 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
571     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
572         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
573     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
574         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
575 };
576
577 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
578     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
579         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
580     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
581         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
582 };
583
584 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
585     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
586         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
587     { BlackRook, BlackKnight, BlackMan, BlackFerz,
588         BlackKing, BlackMan, BlackKnight, BlackRook }
589 };
590
591 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
592     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
593         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
594     { BlackRook, BlackKnight, BlackMan, BlackFerz,
595         BlackKing, BlackMan, BlackKnight, BlackRook }
596 };
597
598 ChessSquare  lionArray[2][BOARD_FILES] = {
599     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
600         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
601     { BlackRook, BlackLion, BlackBishop, BlackQueen,
602         BlackKing, BlackBishop, BlackKnight, BlackRook }
603 };
604
605
606 #if (BOARD_FILES>=10)
607 ChessSquare ShogiArray[2][BOARD_FILES] = {
608     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
609         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
610     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
611         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
612 };
613
614 ChessSquare XiangqiArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
616         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
618         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
619 };
620
621 ChessSquare CapablancaArray[2][BOARD_FILES] = {
622     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
623         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
624     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
625         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
626 };
627
628 ChessSquare GreatArray[2][BOARD_FILES] = {
629     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
630         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
631     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
632         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
633 };
634
635 ChessSquare JanusArray[2][BOARD_FILES] = {
636     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
637         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
638     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
639         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
640 };
641
642 ChessSquare GrandArray[2][BOARD_FILES] = {
643     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
644         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
645     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
646         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
647 };
648
649 ChessSquare ChuChessArray[2][BOARD_FILES] = {
650     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
651         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
652     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
653         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
654 };
655
656 #ifdef GOTHIC
657 ChessSquare GothicArray[2][BOARD_FILES] = {
658     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
659         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
660     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
661         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
662 };
663 #else // !GOTHIC
664 #define GothicArray CapablancaArray
665 #endif // !GOTHIC
666
667 #ifdef FALCON
668 ChessSquare FalconArray[2][BOARD_FILES] = {
669     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
670         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
671     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
672         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
673 };
674 #else // !FALCON
675 #define FalconArray CapablancaArray
676 #endif // !FALCON
677
678 #else // !(BOARD_FILES>=10)
679 #define XiangqiPosition FIDEArray
680 #define CapablancaArray FIDEArray
681 #define GothicArray FIDEArray
682 #define GreatArray FIDEArray
683 #endif // !(BOARD_FILES>=10)
684
685 #if (BOARD_FILES>=12)
686 ChessSquare CourierArray[2][BOARD_FILES] = {
687     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
688         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
689     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
690         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
691 };
692 ChessSquare ChuArray[6][BOARD_FILES] = {
693     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
694       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
695     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
696       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
697     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
698       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
699     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
700       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
701     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
702       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
703     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
704       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
705 };
706 #else // !(BOARD_FILES>=12)
707 #define CourierArray CapablancaArray
708 #define ChuArray CapablancaArray
709 #endif // !(BOARD_FILES>=12)
710
711
712 Board initialPosition;
713
714
715 /* Convert str to a rating. Checks for special cases of "----",
716
717    "++++", etc. Also strips ()'s */
718 int
719 string_to_rating (char *str)
720 {
721   while(*str && !isdigit(*str)) ++str;
722   if (!*str)
723     return 0;   /* One of the special "no rating" cases */
724   else
725     return atoi(str);
726 }
727
728 void
729 ClearProgramStats ()
730 {
731     /* Init programStats */
732     programStats.movelist[0] = 0;
733     programStats.depth = 0;
734     programStats.nr_moves = 0;
735     programStats.moves_left = 0;
736     programStats.nodes = 0;
737     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
738     programStats.score = 0;
739     programStats.got_only_move = 0;
740     programStats.got_fail = 0;
741     programStats.line_is_book = 0;
742 }
743
744 void
745 CommonEngineInit ()
746 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
747     if (appData.firstPlaysBlack) {
748         first.twoMachinesColor = "black\n";
749         second.twoMachinesColor = "white\n";
750     } else {
751         first.twoMachinesColor = "white\n";
752         second.twoMachinesColor = "black\n";
753     }
754
755     first.other = &second;
756     second.other = &first;
757
758     { float norm = 1;
759         if(appData.timeOddsMode) {
760             norm = appData.timeOdds[0];
761             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
762         }
763         first.timeOdds  = appData.timeOdds[0]/norm;
764         second.timeOdds = appData.timeOdds[1]/norm;
765     }
766
767     if(programVersion) free(programVersion);
768     if (appData.noChessProgram) {
769         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
770         sprintf(programVersion, "%s", PACKAGE_STRING);
771     } else {
772       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
773       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
774       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
775     }
776 }
777
778 void
779 UnloadEngine (ChessProgramState *cps)
780 {
781         /* Kill off first chess program */
782         if (cps->isr != NULL)
783           RemoveInputSource(cps->isr);
784         cps->isr = NULL;
785
786         if (cps->pr != NoProc) {
787             ExitAnalyzeMode();
788             DoSleep( appData.delayBeforeQuit );
789             SendToProgram("quit\n", cps);
790             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
791         }
792         cps->pr = NoProc;
793         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
794 }
795
796 void
797 ClearOptions (ChessProgramState *cps)
798 {
799     int i;
800     cps->nrOptions = cps->comboCnt = 0;
801     for(i=0; i<MAX_OPTIONS; i++) {
802         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
803         cps->option[i].textValue = 0;
804     }
805 }
806
807 char *engineNames[] = {
808   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
809      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
810 N_("first"),
811   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
812      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
813 N_("second")
814 };
815
816 void
817 InitEngine (ChessProgramState *cps, int n)
818 {   // [HGM] all engine initialiation put in a function that does one engine
819
820     ClearOptions(cps);
821
822     cps->which = engineNames[n];
823     cps->maybeThinking = FALSE;
824     cps->pr = NoProc;
825     cps->isr = NULL;
826     cps->sendTime = 2;
827     cps->sendDrawOffers = 1;
828
829     cps->program = appData.chessProgram[n];
830     cps->host = appData.host[n];
831     cps->dir = appData.directory[n];
832     cps->initString = appData.engInitString[n];
833     cps->computerString = appData.computerString[n];
834     cps->useSigint  = TRUE;
835     cps->useSigterm = TRUE;
836     cps->reuse = appData.reuse[n];
837     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
838     cps->useSetboard = FALSE;
839     cps->useSAN = FALSE;
840     cps->usePing = FALSE;
841     cps->lastPing = 0;
842     cps->lastPong = 0;
843     cps->usePlayother = FALSE;
844     cps->useColors = TRUE;
845     cps->useUsermove = FALSE;
846     cps->sendICS = FALSE;
847     cps->sendName = appData.icsActive;
848     cps->sdKludge = FALSE;
849     cps->stKludge = FALSE;
850     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
851     TidyProgramName(cps->program, cps->host, cps->tidy);
852     cps->matchWins = 0;
853     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
854     cps->analysisSupport = 2; /* detect */
855     cps->analyzing = FALSE;
856     cps->initDone = FALSE;
857     cps->reload = FALSE;
858     cps->pseudo = appData.pseudo[n];
859
860     /* New features added by Tord: */
861     cps->useFEN960 = FALSE;
862     cps->useOOCastle = TRUE;
863     /* End of new features added by Tord. */
864     cps->fenOverride  = appData.fenOverride[n];
865
866     /* [HGM] time odds: set factor for each machine */
867     cps->timeOdds  = appData.timeOdds[n];
868
869     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
870     cps->accumulateTC = appData.accumulateTC[n];
871     cps->maxNrOfSessions = 1;
872
873     /* [HGM] debug */
874     cps->debug = FALSE;
875
876     cps->drawDepth = appData.drawDepth[n];
877     cps->supportsNPS = UNKNOWN;
878     cps->memSize = FALSE;
879     cps->maxCores = FALSE;
880     ASSIGN(cps->egtFormats, "");
881
882     /* [HGM] options */
883     cps->optionSettings  = appData.engOptions[n];
884
885     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
886     cps->isUCI = appData.isUCI[n]; /* [AS] */
887     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
888     cps->highlight = 0;
889
890     if (appData.protocolVersion[n] > PROTOVER
891         || appData.protocolVersion[n] < 1)
892       {
893         char buf[MSG_SIZ];
894         int len;
895
896         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
897                        appData.protocolVersion[n]);
898         if( (len >= MSG_SIZ) && appData.debugMode )
899           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
900
901         DisplayFatalError(buf, 0, 2);
902       }
903     else
904       {
905         cps->protocolVersion = appData.protocolVersion[n];
906       }
907
908     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
909     ParseFeatures(appData.featureDefaults, cps);
910 }
911
912 ChessProgramState *savCps;
913
914 GameMode oldMode;
915
916 void
917 LoadEngine ()
918 {
919     int i;
920     if(WaitForEngine(savCps, LoadEngine)) return;
921     CommonEngineInit(); // recalculate time odds
922     if(gameInfo.variant != StringToVariant(appData.variant)) {
923         // we changed variant when loading the engine; this forces us to reset
924         Reset(TRUE, savCps != &first);
925         oldMode = BeginningOfGame; // to prevent restoring old mode
926     }
927     InitChessProgram(savCps, FALSE);
928     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
929     DisplayMessage("", "");
930     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
931     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
932     ThawUI();
933     SetGNUMode();
934     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
935 }
936
937 void
938 ReplaceEngine (ChessProgramState *cps, int n)
939 {
940     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
941     keepInfo = 1;
942     if(oldMode != BeginningOfGame) EditGameEvent();
943     keepInfo = 0;
944     UnloadEngine(cps);
945     appData.noChessProgram = FALSE;
946     appData.clockMode = TRUE;
947     InitEngine(cps, n);
948     UpdateLogos(TRUE);
949     if(n) return; // only startup first engine immediately; second can wait
950     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
951     LoadEngine();
952 }
953
954 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
955 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
956
957 static char resetOptions[] =
958         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
959         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
960         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
961         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
962
963 void
964 FloatToFront(char **list, char *engineLine)
965 {
966     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
967     int i=0;
968     if(appData.recentEngines <= 0) return;
969     TidyProgramName(engineLine, "localhost", tidy+1);
970     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
971     strncpy(buf+1, *list, MSG_SIZ-50);
972     if(p = strstr(buf, tidy)) { // tidy name appears in list
973         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
974         while(*p++ = *++q); // squeeze out
975     }
976     strcat(tidy, buf+1); // put list behind tidy name
977     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
978     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
979     ASSIGN(*list, tidy+1);
980 }
981
982 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
983
984 void
985 Load (ChessProgramState *cps, int i)
986 {
987     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
988     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
989         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
990         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
991         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
992         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
993         appData.firstProtocolVersion = PROTOVER;
994         ParseArgsFromString(buf);
995         SwapEngines(i);
996         ReplaceEngine(cps, i);
997         FloatToFront(&appData.recentEngineList, engineLine);
998         return;
999     }
1000     p = engineName;
1001     while(q = strchr(p, SLASH)) p = q+1;
1002     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1003     if(engineDir[0] != NULLCHAR) {
1004         ASSIGN(appData.directory[i], engineDir); p = engineName;
1005     } else if(p != engineName) { // derive directory from engine path, when not given
1006         p[-1] = 0;
1007         ASSIGN(appData.directory[i], engineName);
1008         p[-1] = SLASH;
1009         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1010     } else { ASSIGN(appData.directory[i], "."); }
1011     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1012     if(params[0]) {
1013         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1014         snprintf(command, MSG_SIZ, "%s %s", p, params);
1015         p = command;
1016     }
1017     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1018     ASSIGN(appData.chessProgram[i], p);
1019     appData.isUCI[i] = isUCI;
1020     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1021     appData.hasOwnBookUCI[i] = hasBook;
1022     if(!nickName[0]) useNick = FALSE;
1023     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1024     if(addToList) {
1025         int len;
1026         char quote;
1027         q = firstChessProgramNames;
1028         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1029         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1030         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1031                         quote, p, quote, appData.directory[i],
1032                         useNick ? " -fn \"" : "",
1033                         useNick ? nickName : "",
1034                         useNick ? "\"" : "",
1035                         v1 ? " -firstProtocolVersion 1" : "",
1036                         hasBook ? "" : " -fNoOwnBookUCI",
1037                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1038                         storeVariant ? " -variant " : "",
1039                         storeVariant ? VariantName(gameInfo.variant) : "");
1040         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1041         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1042         if(insert != q) insert[-1] = NULLCHAR;
1043         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1044         if(q)   free(q);
1045         FloatToFront(&appData.recentEngineList, buf);
1046     }
1047     ReplaceEngine(cps, i);
1048 }
1049
1050 void
1051 InitTimeControls ()
1052 {
1053     int matched, min, sec;
1054     /*
1055      * Parse timeControl resource
1056      */
1057     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1058                           appData.movesPerSession)) {
1059         char buf[MSG_SIZ];
1060         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1061         DisplayFatalError(buf, 0, 2);
1062     }
1063
1064     /*
1065      * Parse searchTime resource
1066      */
1067     if (*appData.searchTime != NULLCHAR) {
1068         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1069         if (matched == 1) {
1070             searchTime = min * 60;
1071         } else if (matched == 2) {
1072             searchTime = min * 60 + sec;
1073         } else {
1074             char buf[MSG_SIZ];
1075             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1076             DisplayFatalError(buf, 0, 2);
1077         }
1078     }
1079 }
1080
1081 void
1082 InitBackEnd1 ()
1083 {
1084
1085     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1086     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1087
1088     GetTimeMark(&programStartTime);
1089     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1090     appData.seedBase = random() + (random()<<15);
1091     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1092
1093     ClearProgramStats();
1094     programStats.ok_to_send = 1;
1095     programStats.seen_stat = 0;
1096
1097     /*
1098      * Initialize game list
1099      */
1100     ListNew(&gameList);
1101
1102
1103     /*
1104      * Internet chess server status
1105      */
1106     if (appData.icsActive) {
1107         appData.matchMode = FALSE;
1108         appData.matchGames = 0;
1109 #if ZIPPY
1110         appData.noChessProgram = !appData.zippyPlay;
1111 #else
1112         appData.zippyPlay = FALSE;
1113         appData.zippyTalk = FALSE;
1114         appData.noChessProgram = TRUE;
1115 #endif
1116         if (*appData.icsHelper != NULLCHAR) {
1117             appData.useTelnet = TRUE;
1118             appData.telnetProgram = appData.icsHelper;
1119         }
1120     } else {
1121         appData.zippyTalk = appData.zippyPlay = FALSE;
1122     }
1123
1124     /* [AS] Initialize pv info list [HGM] and game state */
1125     {
1126         int i, j;
1127
1128         for( i=0; i<=framePtr; i++ ) {
1129             pvInfoList[i].depth = -1;
1130             boards[i][EP_STATUS] = EP_NONE;
1131             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1132         }
1133     }
1134
1135     InitTimeControls();
1136
1137     /* [AS] Adjudication threshold */
1138     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1139
1140     InitEngine(&first, 0);
1141     InitEngine(&second, 1);
1142     CommonEngineInit();
1143
1144     pairing.which = "pairing"; // pairing engine
1145     pairing.pr = NoProc;
1146     pairing.isr = NULL;
1147     pairing.program = appData.pairingEngine;
1148     pairing.host = "localhost";
1149     pairing.dir = ".";
1150
1151     if (appData.icsActive) {
1152         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1153     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1154         appData.clockMode = FALSE;
1155         first.sendTime = second.sendTime = 0;
1156     }
1157
1158 #if ZIPPY
1159     /* Override some settings from environment variables, for backward
1160        compatibility.  Unfortunately it's not feasible to have the env
1161        vars just set defaults, at least in xboard.  Ugh.
1162     */
1163     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1164       ZippyInit();
1165     }
1166 #endif
1167
1168     if (!appData.icsActive) {
1169       char buf[MSG_SIZ];
1170       int len;
1171
1172       /* Check for variants that are supported only in ICS mode,
1173          or not at all.  Some that are accepted here nevertheless
1174          have bugs; see comments below.
1175       */
1176       VariantClass variant = StringToVariant(appData.variant);
1177       switch (variant) {
1178       case VariantBughouse:     /* need four players and two boards */
1179       case VariantKriegspiel:   /* need to hide pieces and move details */
1180         /* case VariantFischeRandom: (Fabien: moved below) */
1181         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1182         if( (len >= MSG_SIZ) && appData.debugMode )
1183           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1184
1185         DisplayFatalError(buf, 0, 2);
1186         return;
1187
1188       case VariantUnknown:
1189       case VariantLoadable:
1190       case Variant29:
1191       case Variant30:
1192       case Variant31:
1193       case Variant32:
1194       case Variant33:
1195       case Variant34:
1196       case Variant35:
1197       case Variant36:
1198       default:
1199         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1200         if( (len >= MSG_SIZ) && appData.debugMode )
1201           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1202
1203         DisplayFatalError(buf, 0, 2);
1204         return;
1205
1206       case VariantNormal:     /* definitely works! */
1207         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1208           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1209           return;
1210         }
1211       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1212       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1213       case VariantGothic:     /* [HGM] should work */
1214       case VariantCapablanca: /* [HGM] should work */
1215       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1216       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1217       case VariantChu:        /* [HGM] experimental */
1218       case VariantKnightmate: /* [HGM] should work */
1219       case VariantCylinder:   /* [HGM] untested */
1220       case VariantFalcon:     /* [HGM] untested */
1221       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1222                                  offboard interposition not understood */
1223       case VariantWildCastle: /* pieces not automatically shuffled */
1224       case VariantNoCastle:   /* pieces not automatically shuffled */
1225       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1226       case VariantLosers:     /* should work except for win condition,
1227                                  and doesn't know captures are mandatory */
1228       case VariantSuicide:    /* should work except for win condition,
1229                                  and doesn't know captures are mandatory */
1230       case VariantGiveaway:   /* should work except for win condition,
1231                                  and doesn't know captures are mandatory */
1232       case VariantTwoKings:   /* should work */
1233       case VariantAtomic:     /* should work except for win condition */
1234       case Variant3Check:     /* should work except for win condition */
1235       case VariantShatranj:   /* should work except for all win conditions */
1236       case VariantMakruk:     /* should work except for draw countdown */
1237       case VariantASEAN :     /* should work except for draw countdown */
1238       case VariantBerolina:   /* might work if TestLegality is off */
1239       case VariantCapaRandom: /* should work */
1240       case VariantJanus:      /* should work */
1241       case VariantSuper:      /* experimental */
1242       case VariantGreat:      /* experimental, requires legality testing to be off */
1243       case VariantSChess:     /* S-Chess, should work */
1244       case VariantGrand:      /* should work */
1245       case VariantSpartan:    /* should work */
1246       case VariantLion:       /* should work */
1247       case VariantChuChess:   /* should work */
1248         break;
1249       }
1250     }
1251
1252 }
1253
1254 int
1255 NextIntegerFromString (char ** str, long * value)
1256 {
1257     int result = -1;
1258     char * s = *str;
1259
1260     while( *s == ' ' || *s == '\t' ) {
1261         s++;
1262     }
1263
1264     *value = 0;
1265
1266     if( *s >= '0' && *s <= '9' ) {
1267         while( *s >= '0' && *s <= '9' ) {
1268             *value = *value * 10 + (*s - '0');
1269             s++;
1270         }
1271
1272         result = 0;
1273     }
1274
1275     *str = s;
1276
1277     return result;
1278 }
1279
1280 int
1281 NextTimeControlFromString (char ** str, long * value)
1282 {
1283     long temp;
1284     int result = NextIntegerFromString( str, &temp );
1285
1286     if( result == 0 ) {
1287         *value = temp * 60; /* Minutes */
1288         if( **str == ':' ) {
1289             (*str)++;
1290             result = NextIntegerFromString( str, &temp );
1291             *value += temp; /* Seconds */
1292         }
1293     }
1294
1295     return result;
1296 }
1297
1298 int
1299 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1300 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1301     int result = -1, type = 0; long temp, temp2;
1302
1303     if(**str != ':') return -1; // old params remain in force!
1304     (*str)++;
1305     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1306     if( NextIntegerFromString( str, &temp ) ) return -1;
1307     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1308
1309     if(**str != '/') {
1310         /* time only: incremental or sudden-death time control */
1311         if(**str == '+') { /* increment follows; read it */
1312             (*str)++;
1313             if(**str == '!') type = *(*str)++; // Bronstein TC
1314             if(result = NextIntegerFromString( str, &temp2)) return -1;
1315             *inc = temp2 * 1000;
1316             if(**str == '.') { // read fraction of increment
1317                 char *start = ++(*str);
1318                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1319                 temp2 *= 1000;
1320                 while(start++ < *str) temp2 /= 10;
1321                 *inc += temp2;
1322             }
1323         } else *inc = 0;
1324         *moves = 0; *tc = temp * 1000; *incType = type;
1325         return 0;
1326     }
1327
1328     (*str)++; /* classical time control */
1329     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1330
1331     if(result == 0) {
1332         *moves = temp;
1333         *tc    = temp2 * 1000;
1334         *inc   = 0;
1335         *incType = type;
1336     }
1337     return result;
1338 }
1339
1340 int
1341 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1342 {   /* [HGM] get time to add from the multi-session time-control string */
1343     int incType, moves=1; /* kludge to force reading of first session */
1344     long time, increment;
1345     char *s = tcString;
1346
1347     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1348     do {
1349         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1350         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1351         if(movenr == -1) return time;    /* last move before new session     */
1352         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1353         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1354         if(!moves) return increment;     /* current session is incremental   */
1355         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1356     } while(movenr >= -1);               /* try again for next session       */
1357
1358     return 0; // no new time quota on this move
1359 }
1360
1361 int
1362 ParseTimeControl (char *tc, float ti, int mps)
1363 {
1364   long tc1;
1365   long tc2;
1366   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1367   int min, sec=0;
1368
1369   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1370   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1371       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1372   if(ti > 0) {
1373
1374     if(mps)
1375       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1376     else
1377       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1378   } else {
1379     if(mps)
1380       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1381     else
1382       snprintf(buf, MSG_SIZ, ":%s", mytc);
1383   }
1384   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1385
1386   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1387     return FALSE;
1388   }
1389
1390   if( *tc == '/' ) {
1391     /* Parse second time control */
1392     tc++;
1393
1394     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1395       return FALSE;
1396     }
1397
1398     if( tc2 == 0 ) {
1399       return FALSE;
1400     }
1401
1402     timeControl_2 = tc2 * 1000;
1403   }
1404   else {
1405     timeControl_2 = 0;
1406   }
1407
1408   if( tc1 == 0 ) {
1409     return FALSE;
1410   }
1411
1412   timeControl = tc1 * 1000;
1413
1414   if (ti >= 0) {
1415     timeIncrement = ti * 1000;  /* convert to ms */
1416     movesPerSession = 0;
1417   } else {
1418     timeIncrement = 0;
1419     movesPerSession = mps;
1420   }
1421   return TRUE;
1422 }
1423
1424 void
1425 InitBackEnd2 ()
1426 {
1427     if (appData.debugMode) {
1428 #    ifdef __GIT_VERSION
1429       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1430 #    else
1431       fprintf(debugFP, "Version: %s\n", programVersion);
1432 #    endif
1433     }
1434     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1435
1436     set_cont_sequence(appData.wrapContSeq);
1437     if (appData.matchGames > 0) {
1438         appData.matchMode = TRUE;
1439     } else if (appData.matchMode) {
1440         appData.matchGames = 1;
1441     }
1442     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1443         appData.matchGames = appData.sameColorGames;
1444     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1445         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1446         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1447     }
1448     Reset(TRUE, FALSE);
1449     if (appData.noChessProgram || first.protocolVersion == 1) {
1450       InitBackEnd3();
1451     } else {
1452       /* kludge: allow timeout for initial "feature" commands */
1453       FreezeUI();
1454       DisplayMessage("", _("Starting chess program"));
1455       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1456     }
1457 }
1458
1459 int
1460 CalculateIndex (int index, int gameNr)
1461 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1462     int res;
1463     if(index > 0) return index; // fixed nmber
1464     if(index == 0) return 1;
1465     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1466     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1467     return res;
1468 }
1469
1470 int
1471 LoadGameOrPosition (int gameNr)
1472 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1473     if (*appData.loadGameFile != NULLCHAR) {
1474         if (!LoadGameFromFile(appData.loadGameFile,
1475                 CalculateIndex(appData.loadGameIndex, gameNr),
1476                               appData.loadGameFile, FALSE)) {
1477             DisplayFatalError(_("Bad game file"), 0, 1);
1478             return 0;
1479         }
1480     } else if (*appData.loadPositionFile != NULLCHAR) {
1481         if (!LoadPositionFromFile(appData.loadPositionFile,
1482                 CalculateIndex(appData.loadPositionIndex, gameNr),
1483                                   appData.loadPositionFile)) {
1484             DisplayFatalError(_("Bad position file"), 0, 1);
1485             return 0;
1486         }
1487     }
1488     return 1;
1489 }
1490
1491 void
1492 ReserveGame (int gameNr, char resChar)
1493 {
1494     FILE *tf = fopen(appData.tourneyFile, "r+");
1495     char *p, *q, c, buf[MSG_SIZ];
1496     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1497     safeStrCpy(buf, lastMsg, MSG_SIZ);
1498     DisplayMessage(_("Pick new game"), "");
1499     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1500     ParseArgsFromFile(tf);
1501     p = q = appData.results;
1502     if(appData.debugMode) {
1503       char *r = appData.participants;
1504       fprintf(debugFP, "results = '%s'\n", p);
1505       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1506       fprintf(debugFP, "\n");
1507     }
1508     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1509     nextGame = q - p;
1510     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1511     safeStrCpy(q, p, strlen(p) + 2);
1512     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1513     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1514     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1515         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1516         q[nextGame] = '*';
1517     }
1518     fseek(tf, -(strlen(p)+4), SEEK_END);
1519     c = fgetc(tf);
1520     if(c != '"') // depending on DOS or Unix line endings we can be one off
1521          fseek(tf, -(strlen(p)+2), SEEK_END);
1522     else fseek(tf, -(strlen(p)+3), SEEK_END);
1523     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1524     DisplayMessage(buf, "");
1525     free(p); appData.results = q;
1526     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1527        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1528       int round = appData.defaultMatchGames * appData.tourneyType;
1529       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1530          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1531         UnloadEngine(&first);  // next game belongs to other pairing;
1532         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1533     }
1534     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1535 }
1536
1537 void
1538 MatchEvent (int mode)
1539 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1540         int dummy;
1541         if(matchMode) { // already in match mode: switch it off
1542             abortMatch = TRUE;
1543             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1544             return;
1545         }
1546 //      if(gameMode != BeginningOfGame) {
1547 //          DisplayError(_("You can only start a match from the initial position."), 0);
1548 //          return;
1549 //      }
1550         abortMatch = FALSE;
1551         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1552         /* Set up machine vs. machine match */
1553         nextGame = 0;
1554         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1555         if(appData.tourneyFile[0]) {
1556             ReserveGame(-1, 0);
1557             if(nextGame > appData.matchGames) {
1558                 char buf[MSG_SIZ];
1559                 if(strchr(appData.results, '*') == NULL) {
1560                     FILE *f;
1561                     appData.tourneyCycles++;
1562                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1563                         fclose(f);
1564                         NextTourneyGame(-1, &dummy);
1565                         ReserveGame(-1, 0);
1566                         if(nextGame <= appData.matchGames) {
1567                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1568                             matchMode = mode;
1569                             ScheduleDelayedEvent(NextMatchGame, 10000);
1570                             return;
1571                         }
1572                     }
1573                 }
1574                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1575                 DisplayError(buf, 0);
1576                 appData.tourneyFile[0] = 0;
1577                 return;
1578             }
1579         } else
1580         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1581             DisplayFatalError(_("Can't have a match with no chess programs"),
1582                               0, 2);
1583             return;
1584         }
1585         matchMode = mode;
1586         matchGame = roundNr = 1;
1587         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1588         NextMatchGame();
1589 }
1590
1591 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1592
1593 void
1594 InitBackEnd3 P((void))
1595 {
1596     GameMode initialMode;
1597     char buf[MSG_SIZ];
1598     int err, len;
1599
1600     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1601        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1602         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1603        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1604        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1605         char c, *q = first.variants, *p = strchr(q, ',');
1606         if(p) *p = NULLCHAR;
1607         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1608             int w, h, s;
1609             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1610                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1611             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1612             Reset(TRUE, FALSE);         // and re-initialize
1613         }
1614         if(p) *p = ',';
1615     }
1616
1617     InitChessProgram(&first, startedFromSetupPosition);
1618
1619     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1620         free(programVersion);
1621         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1622         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1623         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1624     }
1625
1626     if (appData.icsActive) {
1627 #ifdef WIN32
1628         /* [DM] Make a console window if needed [HGM] merged ifs */
1629         ConsoleCreate();
1630 #endif
1631         err = establish();
1632         if (err != 0)
1633           {
1634             if (*appData.icsCommPort != NULLCHAR)
1635               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1636                              appData.icsCommPort);
1637             else
1638               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1639                         appData.icsHost, appData.icsPort);
1640
1641             if( (len >= MSG_SIZ) && appData.debugMode )
1642               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1643
1644             DisplayFatalError(buf, err, 1);
1645             return;
1646         }
1647         SetICSMode();
1648         telnetISR =
1649           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1650         fromUserISR =
1651           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1652         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1653             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1654     } else if (appData.noChessProgram) {
1655         SetNCPMode();
1656     } else {
1657         SetGNUMode();
1658     }
1659
1660     if (*appData.cmailGameName != NULLCHAR) {
1661         SetCmailMode();
1662         OpenLoopback(&cmailPR);
1663         cmailISR =
1664           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1665     }
1666
1667     ThawUI();
1668     DisplayMessage("", "");
1669     if (StrCaseCmp(appData.initialMode, "") == 0) {
1670       initialMode = BeginningOfGame;
1671       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1672         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1673         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1674         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1675         ModeHighlight();
1676       }
1677     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1678       initialMode = TwoMachinesPlay;
1679     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1680       initialMode = AnalyzeFile;
1681     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1682       initialMode = AnalyzeMode;
1683     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1684       initialMode = MachinePlaysWhite;
1685     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1686       initialMode = MachinePlaysBlack;
1687     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1688       initialMode = EditGame;
1689     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1690       initialMode = EditPosition;
1691     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1692       initialMode = Training;
1693     } else {
1694       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1695       if( (len >= MSG_SIZ) && appData.debugMode )
1696         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1697
1698       DisplayFatalError(buf, 0, 2);
1699       return;
1700     }
1701
1702     if (appData.matchMode) {
1703         if(appData.tourneyFile[0]) { // start tourney from command line
1704             FILE *f;
1705             if(f = fopen(appData.tourneyFile, "r")) {
1706                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1707                 fclose(f);
1708                 appData.clockMode = TRUE;
1709                 SetGNUMode();
1710             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1711         }
1712         MatchEvent(TRUE);
1713     } else if (*appData.cmailGameName != NULLCHAR) {
1714         /* Set up cmail mode */
1715         ReloadCmailMsgEvent(TRUE);
1716     } else {
1717         /* Set up other modes */
1718         if (initialMode == AnalyzeFile) {
1719           if (*appData.loadGameFile == NULLCHAR) {
1720             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1721             return;
1722           }
1723         }
1724         if (*appData.loadGameFile != NULLCHAR) {
1725             (void) LoadGameFromFile(appData.loadGameFile,
1726                                     appData.loadGameIndex,
1727                                     appData.loadGameFile, TRUE);
1728         } else if (*appData.loadPositionFile != NULLCHAR) {
1729             (void) LoadPositionFromFile(appData.loadPositionFile,
1730                                         appData.loadPositionIndex,
1731                                         appData.loadPositionFile);
1732             /* [HGM] try to make self-starting even after FEN load */
1733             /* to allow automatic setup of fairy variants with wtm */
1734             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1735                 gameMode = BeginningOfGame;
1736                 setboardSpoiledMachineBlack = 1;
1737             }
1738             /* [HGM] loadPos: make that every new game uses the setup */
1739             /* from file as long as we do not switch variant          */
1740             if(!blackPlaysFirst) {
1741                 startedFromPositionFile = TRUE;
1742                 CopyBoard(filePosition, boards[0]);
1743                 CopyBoard(initialPosition, boards[0]);
1744             }
1745         }
1746         if (initialMode == AnalyzeMode) {
1747           if (appData.noChessProgram) {
1748             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1749             return;
1750           }
1751           if (appData.icsActive) {
1752             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1753             return;
1754           }
1755           AnalyzeModeEvent();
1756         } else if (initialMode == AnalyzeFile) {
1757           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1758           ShowThinkingEvent();
1759           AnalyzeFileEvent();
1760           AnalysisPeriodicEvent(1);
1761         } else if (initialMode == MachinePlaysWhite) {
1762           if (appData.noChessProgram) {
1763             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1764                               0, 2);
1765             return;
1766           }
1767           if (appData.icsActive) {
1768             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1769                               0, 2);
1770             return;
1771           }
1772           MachineWhiteEvent();
1773         } else if (initialMode == MachinePlaysBlack) {
1774           if (appData.noChessProgram) {
1775             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1776                               0, 2);
1777             return;
1778           }
1779           if (appData.icsActive) {
1780             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1781                               0, 2);
1782             return;
1783           }
1784           MachineBlackEvent();
1785         } else if (initialMode == TwoMachinesPlay) {
1786           if (appData.noChessProgram) {
1787             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1788                               0, 2);
1789             return;
1790           }
1791           if (appData.icsActive) {
1792             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1793                               0, 2);
1794             return;
1795           }
1796           TwoMachinesEvent();
1797         } else if (initialMode == EditGame) {
1798           EditGameEvent();
1799         } else if (initialMode == EditPosition) {
1800           EditPositionEvent();
1801         } else if (initialMode == Training) {
1802           if (*appData.loadGameFile == NULLCHAR) {
1803             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1804             return;
1805           }
1806           TrainingEvent();
1807         }
1808     }
1809 }
1810
1811 void
1812 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1813 {
1814     DisplayBook(current+1);
1815
1816     MoveHistorySet( movelist, first, last, current, pvInfoList );
1817
1818     EvalGraphSet( first, last, current, pvInfoList );
1819
1820     MakeEngineOutputTitle();
1821 }
1822
1823 /*
1824  * Establish will establish a contact to a remote host.port.
1825  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1826  *  used to talk to the host.
1827  * Returns 0 if okay, error code if not.
1828  */
1829 int
1830 establish ()
1831 {
1832     char buf[MSG_SIZ];
1833
1834     if (*appData.icsCommPort != NULLCHAR) {
1835         /* Talk to the host through a serial comm port */
1836         return OpenCommPort(appData.icsCommPort, &icsPR);
1837
1838     } else if (*appData.gateway != NULLCHAR) {
1839         if (*appData.remoteShell == NULLCHAR) {
1840             /* Use the rcmd protocol to run telnet program on a gateway host */
1841             snprintf(buf, sizeof(buf), "%s %s %s",
1842                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1843             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1844
1845         } else {
1846             /* Use the rsh program to run telnet program on a gateway host */
1847             if (*appData.remoteUser == NULLCHAR) {
1848                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1849                         appData.gateway, appData.telnetProgram,
1850                         appData.icsHost, appData.icsPort);
1851             } else {
1852                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1853                         appData.remoteShell, appData.gateway,
1854                         appData.remoteUser, appData.telnetProgram,
1855                         appData.icsHost, appData.icsPort);
1856             }
1857             return StartChildProcess(buf, "", &icsPR);
1858
1859         }
1860     } else if (appData.useTelnet) {
1861         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1862
1863     } else {
1864         /* TCP socket interface differs somewhat between
1865            Unix and NT; handle details in the front end.
1866            */
1867         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1868     }
1869 }
1870
1871 void
1872 EscapeExpand (char *p, char *q)
1873 {       // [HGM] initstring: routine to shape up string arguments
1874         while(*p++ = *q++) if(p[-1] == '\\')
1875             switch(*q++) {
1876                 case 'n': p[-1] = '\n'; break;
1877                 case 'r': p[-1] = '\r'; break;
1878                 case 't': p[-1] = '\t'; break;
1879                 case '\\': p[-1] = '\\'; break;
1880                 case 0: *p = 0; return;
1881                 default: p[-1] = q[-1]; break;
1882             }
1883 }
1884
1885 void
1886 show_bytes (FILE *fp, char *buf, int count)
1887 {
1888     while (count--) {
1889         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1890             fprintf(fp, "\\%03o", *buf & 0xff);
1891         } else {
1892             putc(*buf, fp);
1893         }
1894         buf++;
1895     }
1896     fflush(fp);
1897 }
1898
1899 /* Returns an errno value */
1900 int
1901 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1902 {
1903     char buf[8192], *p, *q, *buflim;
1904     int left, newcount, outcount;
1905
1906     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1907         *appData.gateway != NULLCHAR) {
1908         if (appData.debugMode) {
1909             fprintf(debugFP, ">ICS: ");
1910             show_bytes(debugFP, message, count);
1911             fprintf(debugFP, "\n");
1912         }
1913         return OutputToProcess(pr, message, count, outError);
1914     }
1915
1916     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1917     p = message;
1918     q = buf;
1919     left = count;
1920     newcount = 0;
1921     while (left) {
1922         if (q >= buflim) {
1923             if (appData.debugMode) {
1924                 fprintf(debugFP, ">ICS: ");
1925                 show_bytes(debugFP, buf, newcount);
1926                 fprintf(debugFP, "\n");
1927             }
1928             outcount = OutputToProcess(pr, buf, newcount, outError);
1929             if (outcount < newcount) return -1; /* to be sure */
1930             q = buf;
1931             newcount = 0;
1932         }
1933         if (*p == '\n') {
1934             *q++ = '\r';
1935             newcount++;
1936         } else if (((unsigned char) *p) == TN_IAC) {
1937             *q++ = (char) TN_IAC;
1938             newcount ++;
1939         }
1940         *q++ = *p++;
1941         newcount++;
1942         left--;
1943     }
1944     if (appData.debugMode) {
1945         fprintf(debugFP, ">ICS: ");
1946         show_bytes(debugFP, buf, newcount);
1947         fprintf(debugFP, "\n");
1948     }
1949     outcount = OutputToProcess(pr, buf, newcount, outError);
1950     if (outcount < newcount) return -1; /* to be sure */
1951     return count;
1952 }
1953
1954 void
1955 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1956 {
1957     int outError, outCount;
1958     static int gotEof = 0;
1959     static FILE *ini;
1960
1961     /* Pass data read from player on to ICS */
1962     if (count > 0) {
1963         gotEof = 0;
1964         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1965         if (outCount < count) {
1966             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1967         }
1968         if(have_sent_ICS_logon == 2) {
1969           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1970             fprintf(ini, "%s", message);
1971             have_sent_ICS_logon = 3;
1972           } else
1973             have_sent_ICS_logon = 1;
1974         } else if(have_sent_ICS_logon == 3) {
1975             fprintf(ini, "%s", message);
1976             fclose(ini);
1977           have_sent_ICS_logon = 1;
1978         }
1979     } else if (count < 0) {
1980         RemoveInputSource(isr);
1981         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1982     } else if (gotEof++ > 0) {
1983         RemoveInputSource(isr);
1984         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1985     }
1986 }
1987
1988 void
1989 KeepAlive ()
1990 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1991     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1992     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1993     SendToICS("date\n");
1994     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1995 }
1996
1997 /* added routine for printf style output to ics */
1998 void
1999 ics_printf (char *format, ...)
2000 {
2001     char buffer[MSG_SIZ];
2002     va_list args;
2003
2004     va_start(args, format);
2005     vsnprintf(buffer, sizeof(buffer), format, args);
2006     buffer[sizeof(buffer)-1] = '\0';
2007     SendToICS(buffer);
2008     va_end(args);
2009 }
2010
2011 void
2012 SendToICS (char *s)
2013 {
2014     int count, outCount, outError;
2015
2016     if (icsPR == NoProc) return;
2017
2018     count = strlen(s);
2019     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2020     if (outCount < count) {
2021         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2022     }
2023 }
2024
2025 /* This is used for sending logon scripts to the ICS. Sending
2026    without a delay causes problems when using timestamp on ICC
2027    (at least on my machine). */
2028 void
2029 SendToICSDelayed (char *s, long msdelay)
2030 {
2031     int count, outCount, outError;
2032
2033     if (icsPR == NoProc) return;
2034
2035     count = strlen(s);
2036     if (appData.debugMode) {
2037         fprintf(debugFP, ">ICS: ");
2038         show_bytes(debugFP, s, count);
2039         fprintf(debugFP, "\n");
2040     }
2041     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2042                                       msdelay);
2043     if (outCount < count) {
2044         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2045     }
2046 }
2047
2048
2049 /* Remove all highlighting escape sequences in s
2050    Also deletes any suffix starting with '('
2051    */
2052 char *
2053 StripHighlightAndTitle (char *s)
2054 {
2055     static char retbuf[MSG_SIZ];
2056     char *p = retbuf;
2057
2058     while (*s != NULLCHAR) {
2059         while (*s == '\033') {
2060             while (*s != NULLCHAR && !isalpha(*s)) s++;
2061             if (*s != NULLCHAR) s++;
2062         }
2063         while (*s != NULLCHAR && *s != '\033') {
2064             if (*s == '(' || *s == '[') {
2065                 *p = NULLCHAR;
2066                 return retbuf;
2067             }
2068             *p++ = *s++;
2069         }
2070     }
2071     *p = NULLCHAR;
2072     return retbuf;
2073 }
2074
2075 /* Remove all highlighting escape sequences in s */
2076 char *
2077 StripHighlight (char *s)
2078 {
2079     static char retbuf[MSG_SIZ];
2080     char *p = retbuf;
2081
2082     while (*s != NULLCHAR) {
2083         while (*s == '\033') {
2084             while (*s != NULLCHAR && !isalpha(*s)) s++;
2085             if (*s != NULLCHAR) s++;
2086         }
2087         while (*s != NULLCHAR && *s != '\033') {
2088             *p++ = *s++;
2089         }
2090     }
2091     *p = NULLCHAR;
2092     return retbuf;
2093 }
2094
2095 char engineVariant[MSG_SIZ];
2096 char *variantNames[] = VARIANT_NAMES;
2097 char *
2098 VariantName (VariantClass v)
2099 {
2100     if(v == VariantUnknown || *engineVariant) return engineVariant;
2101     return variantNames[v];
2102 }
2103
2104
2105 /* Identify a variant from the strings the chess servers use or the
2106    PGN Variant tag names we use. */
2107 VariantClass
2108 StringToVariant (char *e)
2109 {
2110     char *p;
2111     int wnum = -1;
2112     VariantClass v = VariantNormal;
2113     int i, found = FALSE;
2114     char buf[MSG_SIZ], c;
2115     int len;
2116
2117     if (!e) return v;
2118
2119     /* [HGM] skip over optional board-size prefixes */
2120     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2121         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2122         while( *e++ != '_');
2123     }
2124
2125     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2126         v = VariantNormal;
2127         found = TRUE;
2128     } else
2129     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2130       if (p = StrCaseStr(e, variantNames[i])) {
2131         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2132         v = (VariantClass) i;
2133         found = TRUE;
2134         break;
2135       }
2136     }
2137
2138     if (!found) {
2139       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2140           || StrCaseStr(e, "wild/fr")
2141           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2142         v = VariantFischeRandom;
2143       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2144                  (i = 1, p = StrCaseStr(e, "w"))) {
2145         p += i;
2146         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2147         if (isdigit(*p)) {
2148           wnum = atoi(p);
2149         } else {
2150           wnum = -1;
2151         }
2152         switch (wnum) {
2153         case 0: /* FICS only, actually */
2154         case 1:
2155           /* Castling legal even if K starts on d-file */
2156           v = VariantWildCastle;
2157           break;
2158         case 2:
2159         case 3:
2160         case 4:
2161           /* Castling illegal even if K & R happen to start in
2162              normal positions. */
2163           v = VariantNoCastle;
2164           break;
2165         case 5:
2166         case 7:
2167         case 8:
2168         case 10:
2169         case 11:
2170         case 12:
2171         case 13:
2172         case 14:
2173         case 15:
2174         case 18:
2175         case 19:
2176           /* Castling legal iff K & R start in normal positions */
2177           v = VariantNormal;
2178           break;
2179         case 6:
2180         case 20:
2181         case 21:
2182           /* Special wilds for position setup; unclear what to do here */
2183           v = VariantLoadable;
2184           break;
2185         case 9:
2186           /* Bizarre ICC game */
2187           v = VariantTwoKings;
2188           break;
2189         case 16:
2190           v = VariantKriegspiel;
2191           break;
2192         case 17:
2193           v = VariantLosers;
2194           break;
2195         case 22:
2196           v = VariantFischeRandom;
2197           break;
2198         case 23:
2199           v = VariantCrazyhouse;
2200           break;
2201         case 24:
2202           v = VariantBughouse;
2203           break;
2204         case 25:
2205           v = Variant3Check;
2206           break;
2207         case 26:
2208           /* Not quite the same as FICS suicide! */
2209           v = VariantGiveaway;
2210           break;
2211         case 27:
2212           v = VariantAtomic;
2213           break;
2214         case 28:
2215           v = VariantShatranj;
2216           break;
2217
2218         /* Temporary names for future ICC types.  The name *will* change in
2219            the next xboard/WinBoard release after ICC defines it. */
2220         case 29:
2221           v = Variant29;
2222           break;
2223         case 30:
2224           v = Variant30;
2225           break;
2226         case 31:
2227           v = Variant31;
2228           break;
2229         case 32:
2230           v = Variant32;
2231           break;
2232         case 33:
2233           v = Variant33;
2234           break;
2235         case 34:
2236           v = Variant34;
2237           break;
2238         case 35:
2239           v = Variant35;
2240           break;
2241         case 36:
2242           v = Variant36;
2243           break;
2244         case 37:
2245           v = VariantShogi;
2246           break;
2247         case 38:
2248           v = VariantXiangqi;
2249           break;
2250         case 39:
2251           v = VariantCourier;
2252           break;
2253         case 40:
2254           v = VariantGothic;
2255           break;
2256         case 41:
2257           v = VariantCapablanca;
2258           break;
2259         case 42:
2260           v = VariantKnightmate;
2261           break;
2262         case 43:
2263           v = VariantFairy;
2264           break;
2265         case 44:
2266           v = VariantCylinder;
2267           break;
2268         case 45:
2269           v = VariantFalcon;
2270           break;
2271         case 46:
2272           v = VariantCapaRandom;
2273           break;
2274         case 47:
2275           v = VariantBerolina;
2276           break;
2277         case 48:
2278           v = VariantJanus;
2279           break;
2280         case 49:
2281           v = VariantSuper;
2282           break;
2283         case 50:
2284           v = VariantGreat;
2285           break;
2286         case -1:
2287           /* Found "wild" or "w" in the string but no number;
2288              must assume it's normal chess. */
2289           v = VariantNormal;
2290           break;
2291         default:
2292           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2293           if( (len >= MSG_SIZ) && appData.debugMode )
2294             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2295
2296           DisplayError(buf, 0);
2297           v = VariantUnknown;
2298           break;
2299         }
2300       }
2301     }
2302     if (appData.debugMode) {
2303       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2304               e, wnum, VariantName(v));
2305     }
2306     return v;
2307 }
2308
2309 static int leftover_start = 0, leftover_len = 0;
2310 char star_match[STAR_MATCH_N][MSG_SIZ];
2311
2312 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2313    advance *index beyond it, and set leftover_start to the new value of
2314    *index; else return FALSE.  If pattern contains the character '*', it
2315    matches any sequence of characters not containing '\r', '\n', or the
2316    character following the '*' (if any), and the matched sequence(s) are
2317    copied into star_match.
2318    */
2319 int
2320 looking_at ( char *buf, int *index, char *pattern)
2321 {
2322     char *bufp = &buf[*index], *patternp = pattern;
2323     int star_count = 0;
2324     char *matchp = star_match[0];
2325
2326     for (;;) {
2327         if (*patternp == NULLCHAR) {
2328             *index = leftover_start = bufp - buf;
2329             *matchp = NULLCHAR;
2330             return TRUE;
2331         }
2332         if (*bufp == NULLCHAR) return FALSE;
2333         if (*patternp == '*') {
2334             if (*bufp == *(patternp + 1)) {
2335                 *matchp = NULLCHAR;
2336                 matchp = star_match[++star_count];
2337                 patternp += 2;
2338                 bufp++;
2339                 continue;
2340             } else if (*bufp == '\n' || *bufp == '\r') {
2341                 patternp++;
2342                 if (*patternp == NULLCHAR)
2343                   continue;
2344                 else
2345                   return FALSE;
2346             } else {
2347                 *matchp++ = *bufp++;
2348                 continue;
2349             }
2350         }
2351         if (*patternp != *bufp) return FALSE;
2352         patternp++;
2353         bufp++;
2354     }
2355 }
2356
2357 void
2358 SendToPlayer (char *data, int length)
2359 {
2360     int error, outCount;
2361     outCount = OutputToProcess(NoProc, data, length, &error);
2362     if (outCount < length) {
2363         DisplayFatalError(_("Error writing to display"), error, 1);
2364     }
2365 }
2366
2367 void
2368 PackHolding (char packed[], char *holding)
2369 {
2370     char *p = holding;
2371     char *q = packed;
2372     int runlength = 0;
2373     int curr = 9999;
2374     do {
2375         if (*p == curr) {
2376             runlength++;
2377         } else {
2378             switch (runlength) {
2379               case 0:
2380                 break;
2381               case 1:
2382                 *q++ = curr;
2383                 break;
2384               case 2:
2385                 *q++ = curr;
2386                 *q++ = curr;
2387                 break;
2388               default:
2389                 sprintf(q, "%d", runlength);
2390                 while (*q) q++;
2391                 *q++ = curr;
2392                 break;
2393             }
2394             runlength = 1;
2395             curr = *p;
2396         }
2397     } while (*p++);
2398     *q = NULLCHAR;
2399 }
2400
2401 /* Telnet protocol requests from the front end */
2402 void
2403 TelnetRequest (unsigned char ddww, unsigned char option)
2404 {
2405     unsigned char msg[3];
2406     int outCount, outError;
2407
2408     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2409
2410     if (appData.debugMode) {
2411         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2412         switch (ddww) {
2413           case TN_DO:
2414             ddwwStr = "DO";
2415             break;
2416           case TN_DONT:
2417             ddwwStr = "DONT";
2418             break;
2419           case TN_WILL:
2420             ddwwStr = "WILL";
2421             break;
2422           case TN_WONT:
2423             ddwwStr = "WONT";
2424             break;
2425           default:
2426             ddwwStr = buf1;
2427             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2428             break;
2429         }
2430         switch (option) {
2431           case TN_ECHO:
2432             optionStr = "ECHO";
2433             break;
2434           default:
2435             optionStr = buf2;
2436             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2437             break;
2438         }
2439         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2440     }
2441     msg[0] = TN_IAC;
2442     msg[1] = ddww;
2443     msg[2] = option;
2444     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2445     if (outCount < 3) {
2446         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2447     }
2448 }
2449
2450 void
2451 DoEcho ()
2452 {
2453     if (!appData.icsActive) return;
2454     TelnetRequest(TN_DO, TN_ECHO);
2455 }
2456
2457 void
2458 DontEcho ()
2459 {
2460     if (!appData.icsActive) return;
2461     TelnetRequest(TN_DONT, TN_ECHO);
2462 }
2463
2464 void
2465 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2466 {
2467     /* put the holdings sent to us by the server on the board holdings area */
2468     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2469     char p;
2470     ChessSquare piece;
2471
2472     if(gameInfo.holdingsWidth < 2)  return;
2473     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2474         return; // prevent overwriting by pre-board holdings
2475
2476     if( (int)lowestPiece >= BlackPawn ) {
2477         holdingsColumn = 0;
2478         countsColumn = 1;
2479         holdingsStartRow = BOARD_HEIGHT-1;
2480         direction = -1;
2481     } else {
2482         holdingsColumn = BOARD_WIDTH-1;
2483         countsColumn = BOARD_WIDTH-2;
2484         holdingsStartRow = 0;
2485         direction = 1;
2486     }
2487
2488     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2489         board[i][holdingsColumn] = EmptySquare;
2490         board[i][countsColumn]   = (ChessSquare) 0;
2491     }
2492     while( (p=*holdings++) != NULLCHAR ) {
2493         piece = CharToPiece( ToUpper(p) );
2494         if(piece == EmptySquare) continue;
2495         /*j = (int) piece - (int) WhitePawn;*/
2496         j = PieceToNumber(piece);
2497         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2498         if(j < 0) continue;               /* should not happen */
2499         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2500         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2501         board[holdingsStartRow+j*direction][countsColumn]++;
2502     }
2503 }
2504
2505
2506 void
2507 VariantSwitch (Board board, VariantClass newVariant)
2508 {
2509    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2510    static Board oldBoard;
2511
2512    startedFromPositionFile = FALSE;
2513    if(gameInfo.variant == newVariant) return;
2514
2515    /* [HGM] This routine is called each time an assignment is made to
2516     * gameInfo.variant during a game, to make sure the board sizes
2517     * are set to match the new variant. If that means adding or deleting
2518     * holdings, we shift the playing board accordingly
2519     * This kludge is needed because in ICS observe mode, we get boards
2520     * of an ongoing game without knowing the variant, and learn about the
2521     * latter only later. This can be because of the move list we requested,
2522     * in which case the game history is refilled from the beginning anyway,
2523     * but also when receiving holdings of a crazyhouse game. In the latter
2524     * case we want to add those holdings to the already received position.
2525     */
2526
2527
2528    if (appData.debugMode) {
2529      fprintf(debugFP, "Switch board from %s to %s\n",
2530              VariantName(gameInfo.variant), VariantName(newVariant));
2531      setbuf(debugFP, NULL);
2532    }
2533    shuffleOpenings = 0;       /* [HGM] shuffle */
2534    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2535    switch(newVariant)
2536      {
2537      case VariantShogi:
2538        newWidth = 9;  newHeight = 9;
2539        gameInfo.holdingsSize = 7;
2540      case VariantBughouse:
2541      case VariantCrazyhouse:
2542        newHoldingsWidth = 2; break;
2543      case VariantGreat:
2544        newWidth = 10;
2545      case VariantSuper:
2546        newHoldingsWidth = 2;
2547        gameInfo.holdingsSize = 8;
2548        break;
2549      case VariantGothic:
2550      case VariantCapablanca:
2551      case VariantCapaRandom:
2552        newWidth = 10;
2553      default:
2554        newHoldingsWidth = gameInfo.holdingsSize = 0;
2555      };
2556
2557    if(newWidth  != gameInfo.boardWidth  ||
2558       newHeight != gameInfo.boardHeight ||
2559       newHoldingsWidth != gameInfo.holdingsWidth ) {
2560
2561      /* shift position to new playing area, if needed */
2562      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2563        for(i=0; i<BOARD_HEIGHT; i++)
2564          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2565            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2566              board[i][j];
2567        for(i=0; i<newHeight; i++) {
2568          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2569          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2570        }
2571      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2572        for(i=0; i<BOARD_HEIGHT; i++)
2573          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2574            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2575              board[i][j];
2576      }
2577      board[HOLDINGS_SET] = 0;
2578      gameInfo.boardWidth  = newWidth;
2579      gameInfo.boardHeight = newHeight;
2580      gameInfo.holdingsWidth = newHoldingsWidth;
2581      gameInfo.variant = newVariant;
2582      InitDrawingSizes(-2, 0);
2583    } else gameInfo.variant = newVariant;
2584    CopyBoard(oldBoard, board);   // remember correctly formatted board
2585      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2586    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2587 }
2588
2589 static int loggedOn = FALSE;
2590
2591 /*-- Game start info cache: --*/
2592 int gs_gamenum;
2593 char gs_kind[MSG_SIZ];
2594 static char player1Name[128] = "";
2595 static char player2Name[128] = "";
2596 static char cont_seq[] = "\n\\   ";
2597 static int player1Rating = -1;
2598 static int player2Rating = -1;
2599 /*----------------------------*/
2600
2601 ColorClass curColor = ColorNormal;
2602 int suppressKibitz = 0;
2603
2604 // [HGM] seekgraph
2605 Boolean soughtPending = FALSE;
2606 Boolean seekGraphUp;
2607 #define MAX_SEEK_ADS 200
2608 #define SQUARE 0x80
2609 char *seekAdList[MAX_SEEK_ADS];
2610 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2611 float tcList[MAX_SEEK_ADS];
2612 char colorList[MAX_SEEK_ADS];
2613 int nrOfSeekAds = 0;
2614 int minRating = 1010, maxRating = 2800;
2615 int hMargin = 10, vMargin = 20, h, w;
2616 extern int squareSize, lineGap;
2617
2618 void
2619 PlotSeekAd (int i)
2620 {
2621         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2622         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2623         if(r < minRating+100 && r >=0 ) r = minRating+100;
2624         if(r > maxRating) r = maxRating;
2625         if(tc < 1.f) tc = 1.f;
2626         if(tc > 95.f) tc = 95.f;
2627         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2628         y = ((double)r - minRating)/(maxRating - minRating)
2629             * (h-vMargin-squareSize/8-1) + vMargin;
2630         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2631         if(strstr(seekAdList[i], " u ")) color = 1;
2632         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2633            !strstr(seekAdList[i], "bullet") &&
2634            !strstr(seekAdList[i], "blitz") &&
2635            !strstr(seekAdList[i], "standard") ) color = 2;
2636         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2637         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2638 }
2639
2640 void
2641 PlotSingleSeekAd (int i)
2642 {
2643         PlotSeekAd(i);
2644 }
2645
2646 void
2647 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2648 {
2649         char buf[MSG_SIZ], *ext = "";
2650         VariantClass v = StringToVariant(type);
2651         if(strstr(type, "wild")) {
2652             ext = type + 4; // append wild number
2653             if(v == VariantFischeRandom) type = "chess960"; else
2654             if(v == VariantLoadable) type = "setup"; else
2655             type = VariantName(v);
2656         }
2657         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2658         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2659             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2660             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2661             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2662             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2663             seekNrList[nrOfSeekAds] = nr;
2664             zList[nrOfSeekAds] = 0;
2665             seekAdList[nrOfSeekAds++] = StrSave(buf);
2666             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2667         }
2668 }
2669
2670 void
2671 EraseSeekDot (int i)
2672 {
2673     int x = xList[i], y = yList[i], d=squareSize/4, k;
2674     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2675     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2676     // now replot every dot that overlapped
2677     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2678         int xx = xList[k], yy = yList[k];
2679         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2680             DrawSeekDot(xx, yy, colorList[k]);
2681     }
2682 }
2683
2684 void
2685 RemoveSeekAd (int nr)
2686 {
2687         int i;
2688         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2689             EraseSeekDot(i);
2690             if(seekAdList[i]) free(seekAdList[i]);
2691             seekAdList[i] = seekAdList[--nrOfSeekAds];
2692             seekNrList[i] = seekNrList[nrOfSeekAds];
2693             ratingList[i] = ratingList[nrOfSeekAds];
2694             colorList[i]  = colorList[nrOfSeekAds];
2695             tcList[i] = tcList[nrOfSeekAds];
2696             xList[i]  = xList[nrOfSeekAds];
2697             yList[i]  = yList[nrOfSeekAds];
2698             zList[i]  = zList[nrOfSeekAds];
2699             seekAdList[nrOfSeekAds] = NULL;
2700             break;
2701         }
2702 }
2703
2704 Boolean
2705 MatchSoughtLine (char *line)
2706 {
2707     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2708     int nr, base, inc, u=0; char dummy;
2709
2710     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2711        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2712        (u=1) &&
2713        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2714         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2715         // match: compact and save the line
2716         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2717         return TRUE;
2718     }
2719     return FALSE;
2720 }
2721
2722 int
2723 DrawSeekGraph ()
2724 {
2725     int i;
2726     if(!seekGraphUp) return FALSE;
2727     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2728     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2729
2730     DrawSeekBackground(0, 0, w, h);
2731     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2732     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2733     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2734         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2735         yy = h-1-yy;
2736         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2737         if(i%500 == 0) {
2738             char buf[MSG_SIZ];
2739             snprintf(buf, MSG_SIZ, "%d", i);
2740             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2741         }
2742     }
2743     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2744     for(i=1; i<100; i+=(i<10?1:5)) {
2745         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2746         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2747         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2748             char buf[MSG_SIZ];
2749             snprintf(buf, MSG_SIZ, "%d", i);
2750             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2751         }
2752     }
2753     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2754     return TRUE;
2755 }
2756
2757 int
2758 SeekGraphClick (ClickType click, int x, int y, int moving)
2759 {
2760     static int lastDown = 0, displayed = 0, lastSecond;
2761     if(y < 0) return FALSE;
2762     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2763         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2764         if(!seekGraphUp) return FALSE;
2765         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2766         DrawPosition(TRUE, NULL);
2767         return TRUE;
2768     }
2769     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2770         if(click == Release || moving) return FALSE;
2771         nrOfSeekAds = 0;
2772         soughtPending = TRUE;
2773         SendToICS(ics_prefix);
2774         SendToICS("sought\n"); // should this be "sought all"?
2775     } else { // issue challenge based on clicked ad
2776         int dist = 10000; int i, closest = 0, second = 0;
2777         for(i=0; i<nrOfSeekAds; i++) {
2778             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2779             if(d < dist) { dist = d; closest = i; }
2780             second += (d - zList[i] < 120); // count in-range ads
2781             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2782         }
2783         if(dist < 120) {
2784             char buf[MSG_SIZ];
2785             second = (second > 1);
2786             if(displayed != closest || second != lastSecond) {
2787                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2788                 lastSecond = second; displayed = closest;
2789             }
2790             if(click == Press) {
2791                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2792                 lastDown = closest;
2793                 return TRUE;
2794             } // on press 'hit', only show info
2795             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2796             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2797             SendToICS(ics_prefix);
2798             SendToICS(buf);
2799             return TRUE; // let incoming board of started game pop down the graph
2800         } else if(click == Release) { // release 'miss' is ignored
2801             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2802             if(moving == 2) { // right up-click
2803                 nrOfSeekAds = 0; // refresh graph
2804                 soughtPending = TRUE;
2805                 SendToICS(ics_prefix);
2806                 SendToICS("sought\n"); // should this be "sought all"?
2807             }
2808             return TRUE;
2809         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2810         // press miss or release hit 'pop down' seek graph
2811         seekGraphUp = FALSE;
2812         DrawPosition(TRUE, NULL);
2813     }
2814     return TRUE;
2815 }
2816
2817 void
2818 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2819 {
2820 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2821 #define STARTED_NONE 0
2822 #define STARTED_MOVES 1
2823 #define STARTED_BOARD 2
2824 #define STARTED_OBSERVE 3
2825 #define STARTED_HOLDINGS 4
2826 #define STARTED_CHATTER 5
2827 #define STARTED_COMMENT 6
2828 #define STARTED_MOVES_NOHIDE 7
2829
2830     static int started = STARTED_NONE;
2831     static char parse[20000];
2832     static int parse_pos = 0;
2833     static char buf[BUF_SIZE + 1];
2834     static int firstTime = TRUE, intfSet = FALSE;
2835     static ColorClass prevColor = ColorNormal;
2836     static int savingComment = FALSE;
2837     static int cmatch = 0; // continuation sequence match
2838     char *bp;
2839     char str[MSG_SIZ];
2840     int i, oldi;
2841     int buf_len;
2842     int next_out;
2843     int tkind;
2844     int backup;    /* [DM] For zippy color lines */
2845     char *p;
2846     char talker[MSG_SIZ]; // [HGM] chat
2847     int channel, collective=0;
2848
2849     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2850
2851     if (appData.debugMode) {
2852       if (!error) {
2853         fprintf(debugFP, "<ICS: ");
2854         show_bytes(debugFP, data, count);
2855         fprintf(debugFP, "\n");
2856       }
2857     }
2858
2859     if (appData.debugMode) { int f = forwardMostMove;
2860         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2861                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2862                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2863     }
2864     if (count > 0) {
2865         /* If last read ended with a partial line that we couldn't parse,
2866            prepend it to the new read and try again. */
2867         if (leftover_len > 0) {
2868             for (i=0; i<leftover_len; i++)
2869               buf[i] = buf[leftover_start + i];
2870         }
2871
2872     /* copy new characters into the buffer */
2873     bp = buf + leftover_len;
2874     buf_len=leftover_len;
2875     for (i=0; i<count; i++)
2876     {
2877         // ignore these
2878         if (data[i] == '\r')
2879             continue;
2880
2881         // join lines split by ICS?
2882         if (!appData.noJoin)
2883         {
2884             /*
2885                 Joining just consists of finding matches against the
2886                 continuation sequence, and discarding that sequence
2887                 if found instead of copying it.  So, until a match
2888                 fails, there's nothing to do since it might be the
2889                 complete sequence, and thus, something we don't want
2890                 copied.
2891             */
2892             if (data[i] == cont_seq[cmatch])
2893             {
2894                 cmatch++;
2895                 if (cmatch == strlen(cont_seq))
2896                 {
2897                     cmatch = 0; // complete match.  just reset the counter
2898
2899                     /*
2900                         it's possible for the ICS to not include the space
2901                         at the end of the last word, making our [correct]
2902                         join operation fuse two separate words.  the server
2903                         does this when the space occurs at the width setting.
2904                     */
2905                     if (!buf_len || buf[buf_len-1] != ' ')
2906                     {
2907                         *bp++ = ' ';
2908                         buf_len++;
2909                     }
2910                 }
2911                 continue;
2912             }
2913             else if (cmatch)
2914             {
2915                 /*
2916                     match failed, so we have to copy what matched before
2917                     falling through and copying this character.  In reality,
2918                     this will only ever be just the newline character, but
2919                     it doesn't hurt to be precise.
2920                 */
2921                 strncpy(bp, cont_seq, cmatch);
2922                 bp += cmatch;
2923                 buf_len += cmatch;
2924                 cmatch = 0;
2925             }
2926         }
2927
2928         // copy this char
2929         *bp++ = data[i];
2930         buf_len++;
2931     }
2932
2933         buf[buf_len] = NULLCHAR;
2934 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2935         next_out = 0;
2936         leftover_start = 0;
2937
2938         i = 0;
2939         while (i < buf_len) {
2940             /* Deal with part of the TELNET option negotiation
2941                protocol.  We refuse to do anything beyond the
2942                defaults, except that we allow the WILL ECHO option,
2943                which ICS uses to turn off password echoing when we are
2944                directly connected to it.  We reject this option
2945                if localLineEditing mode is on (always on in xboard)
2946                and we are talking to port 23, which might be a real
2947                telnet server that will try to keep WILL ECHO on permanently.
2948              */
2949             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2950                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2951                 unsigned char option;
2952                 oldi = i;
2953                 switch ((unsigned char) buf[++i]) {
2954                   case TN_WILL:
2955                     if (appData.debugMode)
2956                       fprintf(debugFP, "\n<WILL ");
2957                     switch (option = (unsigned char) buf[++i]) {
2958                       case TN_ECHO:
2959                         if (appData.debugMode)
2960                           fprintf(debugFP, "ECHO ");
2961                         /* Reply only if this is a change, according
2962                            to the protocol rules. */
2963                         if (remoteEchoOption) break;
2964                         if (appData.localLineEditing &&
2965                             atoi(appData.icsPort) == TN_PORT) {
2966                             TelnetRequest(TN_DONT, TN_ECHO);
2967                         } else {
2968                             EchoOff();
2969                             TelnetRequest(TN_DO, TN_ECHO);
2970                             remoteEchoOption = TRUE;
2971                         }
2972                         break;
2973                       default:
2974                         if (appData.debugMode)
2975                           fprintf(debugFP, "%d ", option);
2976                         /* Whatever this is, we don't want it. */
2977                         TelnetRequest(TN_DONT, option);
2978                         break;
2979                     }
2980                     break;
2981                   case TN_WONT:
2982                     if (appData.debugMode)
2983                       fprintf(debugFP, "\n<WONT ");
2984                     switch (option = (unsigned char) buf[++i]) {
2985                       case TN_ECHO:
2986                         if (appData.debugMode)
2987                           fprintf(debugFP, "ECHO ");
2988                         /* Reply only if this is a change, according
2989                            to the protocol rules. */
2990                         if (!remoteEchoOption) break;
2991                         EchoOn();
2992                         TelnetRequest(TN_DONT, TN_ECHO);
2993                         remoteEchoOption = FALSE;
2994                         break;
2995                       default:
2996                         if (appData.debugMode)
2997                           fprintf(debugFP, "%d ", (unsigned char) option);
2998                         /* Whatever this is, it must already be turned
2999                            off, because we never agree to turn on
3000                            anything non-default, so according to the
3001                            protocol rules, we don't reply. */
3002                         break;
3003                     }
3004                     break;
3005                   case TN_DO:
3006                     if (appData.debugMode)
3007                       fprintf(debugFP, "\n<DO ");
3008                     switch (option = (unsigned char) buf[++i]) {
3009                       default:
3010                         /* Whatever this is, we refuse to do it. */
3011                         if (appData.debugMode)
3012                           fprintf(debugFP, "%d ", option);
3013                         TelnetRequest(TN_WONT, option);
3014                         break;
3015                     }
3016                     break;
3017                   case TN_DONT:
3018                     if (appData.debugMode)
3019                       fprintf(debugFP, "\n<DONT ");
3020                     switch (option = (unsigned char) buf[++i]) {
3021                       default:
3022                         if (appData.debugMode)
3023                           fprintf(debugFP, "%d ", option);
3024                         /* Whatever this is, we are already not doing
3025                            it, because we never agree to do anything
3026                            non-default, so according to the protocol
3027                            rules, we don't reply. */
3028                         break;
3029                     }
3030                     break;
3031                   case TN_IAC:
3032                     if (appData.debugMode)
3033                       fprintf(debugFP, "\n<IAC ");
3034                     /* Doubled IAC; pass it through */
3035                     i--;
3036                     break;
3037                   default:
3038                     if (appData.debugMode)
3039                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3040                     /* Drop all other telnet commands on the floor */
3041                     break;
3042                 }
3043                 if (oldi > next_out)
3044                   SendToPlayer(&buf[next_out], oldi - next_out);
3045                 if (++i > next_out)
3046                   next_out = i;
3047                 continue;
3048             }
3049
3050             /* OK, this at least will *usually* work */
3051             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3052                 loggedOn = TRUE;
3053             }
3054
3055             if (loggedOn && !intfSet) {
3056                 if (ics_type == ICS_ICC) {
3057                   snprintf(str, MSG_SIZ,
3058                           "/set-quietly interface %s\n/set-quietly style 12\n",
3059                           programVersion);
3060                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3061                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3062                 } else if (ics_type == ICS_CHESSNET) {
3063                   snprintf(str, MSG_SIZ, "/style 12\n");
3064                 } else {
3065                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3066                   strcat(str, programVersion);
3067                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3068                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3069                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3070 #ifdef WIN32
3071                   strcat(str, "$iset nohighlight 1\n");
3072 #endif
3073                   strcat(str, "$iset lock 1\n$style 12\n");
3074                 }
3075                 SendToICS(str);
3076                 NotifyFrontendLogin();
3077                 intfSet = TRUE;
3078             }
3079
3080             if (started == STARTED_COMMENT) {
3081                 /* Accumulate characters in comment */
3082                 parse[parse_pos++] = buf[i];
3083                 if (buf[i] == '\n') {
3084                     parse[parse_pos] = NULLCHAR;
3085                     if(chattingPartner>=0) {
3086                         char mess[MSG_SIZ];
3087                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3088                         OutputChatMessage(chattingPartner, mess);
3089                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3090                             int p;
3091                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3092                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3093                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3094                                 OutputChatMessage(p, mess);
3095                                 break;
3096                             }
3097                         }
3098                         chattingPartner = -1;
3099                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3100                         collective = 0;
3101                     } else
3102                     if(!suppressKibitz) // [HGM] kibitz
3103                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3104                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3105                         int nrDigit = 0, nrAlph = 0, j;
3106                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3107                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3108                         parse[parse_pos] = NULLCHAR;
3109                         // try to be smart: if it does not look like search info, it should go to
3110                         // ICS interaction window after all, not to engine-output window.
3111                         for(j=0; j<parse_pos; j++) { // count letters and digits
3112                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3113                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3114                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3115                         }
3116                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3117                             int depth=0; float score;
3118                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3119                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3120                                 pvInfoList[forwardMostMove-1].depth = depth;
3121                                 pvInfoList[forwardMostMove-1].score = 100*score;
3122                             }
3123                             OutputKibitz(suppressKibitz, parse);
3124                         } else {
3125                             char tmp[MSG_SIZ];
3126                             if(gameMode == IcsObserving) // restore original ICS messages
3127                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3128                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3129                             else
3130                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3131                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3132                             SendToPlayer(tmp, strlen(tmp));
3133                         }
3134                         next_out = i+1; // [HGM] suppress printing in ICS window
3135                     }
3136                     started = STARTED_NONE;
3137                 } else {
3138                     /* Don't match patterns against characters in comment */
3139                     i++;
3140                     continue;
3141                 }
3142             }
3143             if (started == STARTED_CHATTER) {
3144                 if (buf[i] != '\n') {
3145                     /* Don't match patterns against characters in chatter */
3146                     i++;
3147                     continue;
3148                 }
3149                 started = STARTED_NONE;
3150                 if(suppressKibitz) next_out = i+1;
3151             }
3152
3153             /* Kludge to deal with rcmd protocol */
3154             if (firstTime && looking_at(buf, &i, "\001*")) {
3155                 DisplayFatalError(&buf[1], 0, 1);
3156                 continue;
3157             } else {
3158                 firstTime = FALSE;
3159             }
3160
3161             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3162                 ics_type = ICS_ICC;
3163                 ics_prefix = "/";
3164                 if (appData.debugMode)
3165                   fprintf(debugFP, "ics_type %d\n", ics_type);
3166                 continue;
3167             }
3168             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3169                 ics_type = ICS_FICS;
3170                 ics_prefix = "$";
3171                 if (appData.debugMode)
3172                   fprintf(debugFP, "ics_type %d\n", ics_type);
3173                 continue;
3174             }
3175             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3176                 ics_type = ICS_CHESSNET;
3177                 ics_prefix = "/";
3178                 if (appData.debugMode)
3179                   fprintf(debugFP, "ics_type %d\n", ics_type);
3180                 continue;
3181             }
3182
3183             if (!loggedOn &&
3184                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3185                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3186                  looking_at(buf, &i, "will be \"*\""))) {
3187               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3188               continue;
3189             }
3190
3191             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3192               char buf[MSG_SIZ];
3193               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3194               DisplayIcsInteractionTitle(buf);
3195               have_set_title = TRUE;
3196             }
3197
3198             /* skip finger notes */
3199             if (started == STARTED_NONE &&
3200                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3201                  (buf[i] == '1' && buf[i+1] == '0')) &&
3202                 buf[i+2] == ':' && buf[i+3] == ' ') {
3203               started = STARTED_CHATTER;
3204               i += 3;
3205               continue;
3206             }
3207
3208             oldi = i;
3209             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3210             if(appData.seekGraph) {
3211                 if(soughtPending && MatchSoughtLine(buf+i)) {
3212                     i = strstr(buf+i, "rated") - buf;
3213                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3214                     next_out = leftover_start = i;
3215                     started = STARTED_CHATTER;
3216                     suppressKibitz = TRUE;
3217                     continue;
3218                 }
3219                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3220                         && looking_at(buf, &i, "* ads displayed")) {
3221                     soughtPending = FALSE;
3222                     seekGraphUp = TRUE;
3223                     DrawSeekGraph();
3224                     continue;
3225                 }
3226                 if(appData.autoRefresh) {
3227                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3228                         int s = (ics_type == ICS_ICC); // ICC format differs
3229                         if(seekGraphUp)
3230                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3231                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3232                         looking_at(buf, &i, "*% "); // eat prompt
3233                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3234                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3235                         next_out = i; // suppress
3236                         continue;
3237                     }
3238                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3239                         char *p = star_match[0];
3240                         while(*p) {
3241                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3242                             while(*p && *p++ != ' '); // next
3243                         }
3244                         looking_at(buf, &i, "*% "); // eat prompt
3245                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3246                         next_out = i;
3247                         continue;
3248                     }
3249                 }
3250             }
3251
3252             /* skip formula vars */
3253             if (started == STARTED_NONE &&
3254                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3255               started = STARTED_CHATTER;
3256               i += 3;
3257               continue;
3258             }
3259
3260             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3261             if (appData.autoKibitz && started == STARTED_NONE &&
3262                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3263                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3264                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3265                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3266                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3267                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3268                         suppressKibitz = TRUE;
3269                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3270                         next_out = i;
3271                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3272                                 && (gameMode == IcsPlayingWhite)) ||
3273                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3274                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3275                             started = STARTED_CHATTER; // own kibitz we simply discard
3276                         else {
3277                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3278                             parse_pos = 0; parse[0] = NULLCHAR;
3279                             savingComment = TRUE;
3280                             suppressKibitz = gameMode != IcsObserving ? 2 :
3281                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3282                         }
3283                         continue;
3284                 } else
3285                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3286                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3287                          && atoi(star_match[0])) {
3288                     // suppress the acknowledgements of our own autoKibitz
3289                     char *p;
3290                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3291                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3292                     SendToPlayer(star_match[0], strlen(star_match[0]));
3293                     if(looking_at(buf, &i, "*% ")) // eat prompt
3294                         suppressKibitz = FALSE;
3295                     next_out = i;
3296                     continue;
3297                 }
3298             } // [HGM] kibitz: end of patch
3299
3300             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3301
3302             // [HGM] chat: intercept tells by users for which we have an open chat window
3303             channel = -1;
3304             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3305                                            looking_at(buf, &i, "* whispers:") ||
3306                                            looking_at(buf, &i, "* kibitzes:") ||
3307                                            looking_at(buf, &i, "* shouts:") ||
3308                                            looking_at(buf, &i, "* c-shouts:") ||
3309                                            looking_at(buf, &i, "--> * ") ||
3310                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3311                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3312                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3313                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3314                 int p;
3315                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3316                 chattingPartner = -1; collective = 0;
3317
3318                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3319                 for(p=0; p<MAX_CHAT; p++) {
3320                     collective = 1;
3321                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3322                     talker[0] = '['; strcat(talker, "] ");
3323                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3324                     chattingPartner = p; break;
3325                     }
3326                 } else
3327                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3328                 for(p=0; p<MAX_CHAT; p++) {
3329                     collective = 1;
3330                     if(!strcmp("kibitzes", chatPartner[p])) {
3331                         talker[0] = '['; strcat(talker, "] ");
3332                         chattingPartner = p; break;
3333                     }
3334                 } else
3335                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3336                 for(p=0; p<MAX_CHAT; p++) {
3337                     collective = 1;
3338                     if(!strcmp("whispers", chatPartner[p])) {
3339                         talker[0] = '['; strcat(talker, "] ");
3340                         chattingPartner = p; break;
3341                     }
3342                 } else
3343                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3344                   if(buf[i-8] == '-' && buf[i-3] == 't')
3345                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3346                     collective = 1;
3347                     if(!strcmp("c-shouts", chatPartner[p])) {
3348                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3349                         chattingPartner = p; break;
3350                     }
3351                   }
3352                   if(chattingPartner < 0)
3353                   for(p=0; p<MAX_CHAT; p++) {
3354                     collective = 1;
3355                     if(!strcmp("shouts", chatPartner[p])) {
3356                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3357                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3358                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3359                         chattingPartner = p; break;
3360                     }
3361                   }
3362                 }
3363                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3364                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3365                     talker[0] = 0;
3366                     Colorize(ColorTell, FALSE);
3367                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3368                     collective |= 2;
3369                     chattingPartner = p; break;
3370                 }
3371                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3372                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3373                     started = STARTED_COMMENT;
3374                     parse_pos = 0; parse[0] = NULLCHAR;
3375                     savingComment = 3 + chattingPartner; // counts as TRUE
3376                     if(collective == 3) i = oldi; else {
3377                         suppressKibitz = TRUE;
3378                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3379                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3380                         continue;
3381                     }
3382                 }
3383             } // [HGM] chat: end of patch
3384
3385           backup = i;
3386             if (appData.zippyTalk || appData.zippyPlay) {
3387                 /* [DM] Backup address for color zippy lines */
3388 #if ZIPPY
3389                if (loggedOn == TRUE)
3390                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3391                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3392 #endif
3393             } // [DM] 'else { ' deleted
3394                 if (
3395                     /* Regular tells and says */
3396                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3397                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3398                     looking_at(buf, &i, "* says: ") ||
3399                     /* Don't color "message" or "messages" output */
3400                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3401                     looking_at(buf, &i, "*. * at *:*: ") ||
3402                     looking_at(buf, &i, "--* (*:*): ") ||
3403                     /* Message notifications (same color as tells) */
3404                     looking_at(buf, &i, "* has left a message ") ||
3405                     looking_at(buf, &i, "* just sent you a message:\n") ||
3406                     /* Whispers and kibitzes */
3407                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3408                     looking_at(buf, &i, "* kibitzes: ") ||
3409                     /* Channel tells */
3410                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3411
3412                   if (tkind == 1 && strchr(star_match[0], ':')) {
3413                       /* Avoid "tells you:" spoofs in channels */
3414                      tkind = 3;
3415                   }
3416                   if (star_match[0][0] == NULLCHAR ||
3417                       strchr(star_match[0], ' ') ||
3418                       (tkind == 3 && strchr(star_match[1], ' '))) {
3419                     /* Reject bogus matches */
3420                     i = oldi;
3421                   } else {
3422                     if (appData.colorize) {
3423                       if (oldi > next_out) {
3424                         SendToPlayer(&buf[next_out], oldi - next_out);
3425                         next_out = oldi;
3426                       }
3427                       switch (tkind) {
3428                       case 1:
3429                         Colorize(ColorTell, FALSE);
3430                         curColor = ColorTell;
3431                         break;
3432                       case 2:
3433                         Colorize(ColorKibitz, FALSE);
3434                         curColor = ColorKibitz;
3435                         break;
3436                       case 3:
3437                         p = strrchr(star_match[1], '(');
3438                         if (p == NULL) {
3439                           p = star_match[1];
3440                         } else {
3441                           p++;
3442                         }
3443                         if (atoi(p) == 1) {
3444                           Colorize(ColorChannel1, FALSE);
3445                           curColor = ColorChannel1;
3446                         } else {
3447                           Colorize(ColorChannel, FALSE);
3448                           curColor = ColorChannel;
3449                         }
3450                         break;
3451                       case 5:
3452                         curColor = ColorNormal;
3453                         break;
3454                       }
3455                     }
3456                     if (started == STARTED_NONE && appData.autoComment &&
3457                         (gameMode == IcsObserving ||
3458                          gameMode == IcsPlayingWhite ||
3459                          gameMode == IcsPlayingBlack)) {
3460                       parse_pos = i - oldi;
3461                       memcpy(parse, &buf[oldi], parse_pos);
3462                       parse[parse_pos] = NULLCHAR;
3463                       started = STARTED_COMMENT;
3464                       savingComment = TRUE;
3465                     } else if(collective != 3) {
3466                       started = STARTED_CHATTER;
3467                       savingComment = FALSE;
3468                     }
3469                     loggedOn = TRUE;
3470                     continue;
3471                   }
3472                 }
3473
3474                 if (looking_at(buf, &i, "* s-shouts: ") ||
3475                     looking_at(buf, &i, "* c-shouts: ")) {
3476                     if (appData.colorize) {
3477                         if (oldi > next_out) {
3478                             SendToPlayer(&buf[next_out], oldi - next_out);
3479                             next_out = oldi;
3480                         }
3481                         Colorize(ColorSShout, FALSE);
3482                         curColor = ColorSShout;
3483                     }
3484                     loggedOn = TRUE;
3485                     started = STARTED_CHATTER;
3486                     continue;
3487                 }
3488
3489                 if (looking_at(buf, &i, "--->")) {
3490                     loggedOn = TRUE;
3491                     continue;
3492                 }
3493
3494                 if (looking_at(buf, &i, "* shouts: ") ||
3495                     looking_at(buf, &i, "--> ")) {
3496                     if (appData.colorize) {
3497                         if (oldi > next_out) {
3498                             SendToPlayer(&buf[next_out], oldi - next_out);
3499                             next_out = oldi;
3500                         }
3501                         Colorize(ColorShout, FALSE);
3502                         curColor = ColorShout;
3503                     }
3504                     loggedOn = TRUE;
3505                     started = STARTED_CHATTER;
3506                     continue;
3507                 }
3508
3509                 if (looking_at( buf, &i, "Challenge:")) {
3510                     if (appData.colorize) {
3511                         if (oldi > next_out) {
3512                             SendToPlayer(&buf[next_out], oldi - next_out);
3513                             next_out = oldi;
3514                         }
3515                         Colorize(ColorChallenge, FALSE);
3516                         curColor = ColorChallenge;
3517                     }
3518                     loggedOn = TRUE;
3519                     continue;
3520                 }
3521
3522                 if (looking_at(buf, &i, "* offers you") ||
3523                     looking_at(buf, &i, "* offers to be") ||
3524                     looking_at(buf, &i, "* would like to") ||
3525                     looking_at(buf, &i, "* requests to") ||
3526                     looking_at(buf, &i, "Your opponent offers") ||
3527                     looking_at(buf, &i, "Your opponent requests")) {
3528
3529                     if (appData.colorize) {
3530                         if (oldi > next_out) {
3531                             SendToPlayer(&buf[next_out], oldi - next_out);
3532                             next_out = oldi;
3533                         }
3534                         Colorize(ColorRequest, FALSE);
3535                         curColor = ColorRequest;
3536                     }
3537                     continue;
3538                 }
3539
3540                 if (looking_at(buf, &i, "* (*) seeking")) {
3541                     if (appData.colorize) {
3542                         if (oldi > next_out) {
3543                             SendToPlayer(&buf[next_out], oldi - next_out);
3544                             next_out = oldi;
3545                         }
3546                         Colorize(ColorSeek, FALSE);
3547                         curColor = ColorSeek;
3548                     }
3549                     continue;
3550             }
3551
3552           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3553
3554             if (looking_at(buf, &i, "\\   ")) {
3555                 if (prevColor != ColorNormal) {
3556                     if (oldi > next_out) {
3557                         SendToPlayer(&buf[next_out], oldi - next_out);
3558                         next_out = oldi;
3559                     }
3560                     Colorize(prevColor, TRUE);
3561                     curColor = prevColor;
3562                 }
3563                 if (savingComment) {
3564                     parse_pos = i - oldi;
3565                     memcpy(parse, &buf[oldi], parse_pos);
3566                     parse[parse_pos] = NULLCHAR;
3567                     started = STARTED_COMMENT;
3568                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3569                         chattingPartner = savingComment - 3; // kludge to remember the box
3570                 } else {
3571                     started = STARTED_CHATTER;
3572                 }
3573                 continue;
3574             }
3575
3576             if (looking_at(buf, &i, "Black Strength :") ||
3577                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3578                 looking_at(buf, &i, "<10>") ||
3579                 looking_at(buf, &i, "#@#")) {
3580                 /* Wrong board style */
3581                 loggedOn = TRUE;
3582                 SendToICS(ics_prefix);
3583                 SendToICS("set style 12\n");
3584                 SendToICS(ics_prefix);
3585                 SendToICS("refresh\n");
3586                 continue;
3587             }
3588
3589             if (looking_at(buf, &i, "login:")) {
3590               if (!have_sent_ICS_logon) {
3591                 if(ICSInitScript())
3592                   have_sent_ICS_logon = 1;
3593                 else // no init script was found
3594                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3595               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3596                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3597               }
3598                 continue;
3599             }
3600
3601             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3602                 (looking_at(buf, &i, "\n<12> ") ||
3603                  looking_at(buf, &i, "<12> "))) {
3604                 loggedOn = TRUE;
3605                 if (oldi > next_out) {
3606                     SendToPlayer(&buf[next_out], oldi - next_out);
3607                 }
3608                 next_out = i;
3609                 started = STARTED_BOARD;
3610                 parse_pos = 0;
3611                 continue;
3612             }
3613
3614             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3615                 looking_at(buf, &i, "<b1> ")) {
3616                 if (oldi > next_out) {
3617                     SendToPlayer(&buf[next_out], oldi - next_out);
3618                 }
3619                 next_out = i;
3620                 started = STARTED_HOLDINGS;
3621                 parse_pos = 0;
3622                 continue;
3623             }
3624
3625             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3626                 loggedOn = TRUE;
3627                 /* Header for a move list -- first line */
3628
3629                 switch (ics_getting_history) {
3630                   case H_FALSE:
3631                     switch (gameMode) {
3632                       case IcsIdle:
3633                       case BeginningOfGame:
3634                         /* User typed "moves" or "oldmoves" while we
3635                            were idle.  Pretend we asked for these
3636                            moves and soak them up so user can step
3637                            through them and/or save them.
3638                            */
3639                         Reset(FALSE, TRUE);
3640                         gameMode = IcsObserving;
3641                         ModeHighlight();
3642                         ics_gamenum = -1;
3643                         ics_getting_history = H_GOT_UNREQ_HEADER;
3644                         break;
3645                       case EditGame: /*?*/
3646                       case EditPosition: /*?*/
3647                         /* Should above feature work in these modes too? */
3648                         /* For now it doesn't */
3649                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3650                         break;
3651                       default:
3652                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3653                         break;
3654                     }
3655                     break;
3656                   case H_REQUESTED:
3657                     /* Is this the right one? */
3658                     if (gameInfo.white && gameInfo.black &&
3659                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3660                         strcmp(gameInfo.black, star_match[2]) == 0) {
3661                         /* All is well */
3662                         ics_getting_history = H_GOT_REQ_HEADER;
3663                     }
3664                     break;
3665                   case H_GOT_REQ_HEADER:
3666                   case H_GOT_UNREQ_HEADER:
3667                   case H_GOT_UNWANTED_HEADER:
3668                   case H_GETTING_MOVES:
3669                     /* Should not happen */
3670                     DisplayError(_("Error gathering move list: two headers"), 0);
3671                     ics_getting_history = H_FALSE;
3672                     break;
3673                 }
3674
3675                 /* Save player ratings into gameInfo if needed */
3676                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3677                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3678                     (gameInfo.whiteRating == -1 ||
3679                      gameInfo.blackRating == -1)) {
3680
3681                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3682                     gameInfo.blackRating = string_to_rating(star_match[3]);
3683                     if (appData.debugMode)
3684                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3685                               gameInfo.whiteRating, gameInfo.blackRating);
3686                 }
3687                 continue;
3688             }
3689
3690             if (looking_at(buf, &i,
3691               "* * match, initial time: * minute*, increment: * second")) {
3692                 /* Header for a move list -- second line */
3693                 /* Initial board will follow if this is a wild game */
3694                 if (gameInfo.event != NULL) free(gameInfo.event);
3695                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3696                 gameInfo.event = StrSave(str);
3697                 /* [HGM] we switched variant. Translate boards if needed. */
3698                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3699                 continue;
3700             }
3701
3702             if (looking_at(buf, &i, "Move  ")) {
3703                 /* Beginning of a move list */
3704                 switch (ics_getting_history) {
3705                   case H_FALSE:
3706                     /* Normally should not happen */
3707                     /* Maybe user hit reset while we were parsing */
3708                     break;
3709                   case H_REQUESTED:
3710                     /* Happens if we are ignoring a move list that is not
3711                      * the one we just requested.  Common if the user
3712                      * tries to observe two games without turning off
3713                      * getMoveList */
3714                     break;
3715                   case H_GETTING_MOVES:
3716                     /* Should not happen */
3717                     DisplayError(_("Error gathering move list: nested"), 0);
3718                     ics_getting_history = H_FALSE;
3719                     break;
3720                   case H_GOT_REQ_HEADER:
3721                     ics_getting_history = H_GETTING_MOVES;
3722                     started = STARTED_MOVES;
3723                     parse_pos = 0;
3724                     if (oldi > next_out) {
3725                         SendToPlayer(&buf[next_out], oldi - next_out);
3726                     }
3727                     break;
3728                   case H_GOT_UNREQ_HEADER:
3729                     ics_getting_history = H_GETTING_MOVES;
3730                     started = STARTED_MOVES_NOHIDE;
3731                     parse_pos = 0;
3732                     break;
3733                   case H_GOT_UNWANTED_HEADER:
3734                     ics_getting_history = H_FALSE;
3735                     break;
3736                 }
3737                 continue;
3738             }
3739
3740             if (looking_at(buf, &i, "% ") ||
3741                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3742                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3743                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3744                     soughtPending = FALSE;
3745                     seekGraphUp = TRUE;
3746                     DrawSeekGraph();
3747                 }
3748                 if(suppressKibitz) next_out = i;
3749                 savingComment = FALSE;
3750                 suppressKibitz = 0;
3751                 switch (started) {
3752                   case STARTED_MOVES:
3753                   case STARTED_MOVES_NOHIDE:
3754                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3755                     parse[parse_pos + i - oldi] = NULLCHAR;
3756                     ParseGameHistory(parse);
3757 #if ZIPPY
3758                     if (appData.zippyPlay && first.initDone) {
3759                         FeedMovesToProgram(&first, forwardMostMove);
3760                         if (gameMode == IcsPlayingWhite) {
3761                             if (WhiteOnMove(forwardMostMove)) {
3762                                 if (first.sendTime) {
3763                                   if (first.useColors) {
3764                                     SendToProgram("black\n", &first);
3765                                   }
3766                                   SendTimeRemaining(&first, TRUE);
3767                                 }
3768                                 if (first.useColors) {
3769                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3770                                 }
3771                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3772                                 first.maybeThinking = TRUE;
3773                             } else {
3774                                 if (first.usePlayother) {
3775                                   if (first.sendTime) {
3776                                     SendTimeRemaining(&first, TRUE);
3777                                   }
3778                                   SendToProgram("playother\n", &first);
3779                                   firstMove = FALSE;
3780                                 } else {
3781                                   firstMove = TRUE;
3782                                 }
3783                             }
3784                         } else if (gameMode == IcsPlayingBlack) {
3785                             if (!WhiteOnMove(forwardMostMove)) {
3786                                 if (first.sendTime) {
3787                                   if (first.useColors) {
3788                                     SendToProgram("white\n", &first);
3789                                   }
3790                                   SendTimeRemaining(&first, FALSE);
3791                                 }
3792                                 if (first.useColors) {
3793                                   SendToProgram("black\n", &first);
3794                                 }
3795                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3796                                 first.maybeThinking = TRUE;
3797                             } else {
3798                                 if (first.usePlayother) {
3799                                   if (first.sendTime) {
3800                                     SendTimeRemaining(&first, FALSE);
3801                                   }
3802                                   SendToProgram("playother\n", &first);
3803                                   firstMove = FALSE;
3804                                 } else {
3805                                   firstMove = TRUE;
3806                                 }
3807                             }
3808                         }
3809                     }
3810 #endif
3811                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3812                         /* Moves came from oldmoves or moves command
3813                            while we weren't doing anything else.
3814                            */
3815                         currentMove = forwardMostMove;
3816                         ClearHighlights();/*!!could figure this out*/
3817                         flipView = appData.flipView;
3818                         DrawPosition(TRUE, boards[currentMove]);
3819                         DisplayBothClocks();
3820                         snprintf(str, MSG_SIZ, "%s %s %s",
3821                                 gameInfo.white, _("vs."),  gameInfo.black);
3822                         DisplayTitle(str);
3823                         gameMode = IcsIdle;
3824                     } else {
3825                         /* Moves were history of an active game */
3826                         if (gameInfo.resultDetails != NULL) {
3827                             free(gameInfo.resultDetails);
3828                             gameInfo.resultDetails = NULL;
3829                         }
3830                     }
3831                     HistorySet(parseList, backwardMostMove,
3832                                forwardMostMove, currentMove-1);
3833                     DisplayMove(currentMove - 1);
3834                     if (started == STARTED_MOVES) next_out = i;
3835                     started = STARTED_NONE;
3836                     ics_getting_history = H_FALSE;
3837                     break;
3838
3839                   case STARTED_OBSERVE:
3840                     started = STARTED_NONE;
3841                     SendToICS(ics_prefix);
3842                     SendToICS("refresh\n");
3843                     break;
3844
3845                   default:
3846                     break;
3847                 }
3848                 if(bookHit) { // [HGM] book: simulate book reply
3849                     static char bookMove[MSG_SIZ]; // a bit generous?
3850
3851                     programStats.nodes = programStats.depth = programStats.time =
3852                     programStats.score = programStats.got_only_move = 0;
3853                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3854
3855                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3856                     strcat(bookMove, bookHit);
3857                     HandleMachineMove(bookMove, &first);
3858                 }
3859                 continue;
3860             }
3861
3862             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3863                  started == STARTED_HOLDINGS ||
3864                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3865                 /* Accumulate characters in move list or board */
3866                 parse[parse_pos++] = buf[i];
3867             }
3868
3869             /* Start of game messages.  Mostly we detect start of game
3870                when the first board image arrives.  On some versions
3871                of the ICS, though, we need to do a "refresh" after starting
3872                to observe in order to get the current board right away. */
3873             if (looking_at(buf, &i, "Adding game * to observation list")) {
3874                 started = STARTED_OBSERVE;
3875                 continue;
3876             }
3877
3878             /* Handle auto-observe */
3879             if (appData.autoObserve &&
3880                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3881                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3882                 char *player;
3883                 /* Choose the player that was highlighted, if any. */
3884                 if (star_match[0][0] == '\033' ||
3885                     star_match[1][0] != '\033') {
3886                     player = star_match[0];
3887                 } else {
3888                     player = star_match[2];
3889                 }
3890                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3891                         ics_prefix, StripHighlightAndTitle(player));
3892                 SendToICS(str);
3893
3894                 /* Save ratings from notify string */
3895                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3896                 player1Rating = string_to_rating(star_match[1]);
3897                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3898                 player2Rating = string_to_rating(star_match[3]);
3899
3900                 if (appData.debugMode)
3901                   fprintf(debugFP,
3902                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3903                           player1Name, player1Rating,
3904                           player2Name, player2Rating);
3905
3906                 continue;
3907             }
3908
3909             /* Deal with automatic examine mode after a game,
3910                and with IcsObserving -> IcsExamining transition */
3911             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3912                 looking_at(buf, &i, "has made you an examiner of game *")) {
3913
3914                 int gamenum = atoi(star_match[0]);
3915                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3916                     gamenum == ics_gamenum) {
3917                     /* We were already playing or observing this game;
3918                        no need to refetch history */
3919                     gameMode = IcsExamining;
3920                     if (pausing) {
3921                         pauseExamForwardMostMove = forwardMostMove;
3922                     } else if (currentMove < forwardMostMove) {
3923                         ForwardInner(forwardMostMove);
3924                     }
3925                 } else {
3926                     /* I don't think this case really can happen */
3927                     SendToICS(ics_prefix);
3928                     SendToICS("refresh\n");
3929                 }
3930                 continue;
3931             }
3932
3933             /* Error messages */
3934 //          if (ics_user_moved) {
3935             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3936                 if (looking_at(buf, &i, "Illegal move") ||
3937                     looking_at(buf, &i, "Not a legal move") ||
3938                     looking_at(buf, &i, "Your king is in check") ||
3939                     looking_at(buf, &i, "It isn't your turn") ||
3940                     looking_at(buf, &i, "It is not your move")) {
3941                     /* Illegal move */
3942                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3943                         currentMove = forwardMostMove-1;
3944                         DisplayMove(currentMove - 1); /* before DMError */
3945                         DrawPosition(FALSE, boards[currentMove]);
3946                         SwitchClocks(forwardMostMove-1); // [HGM] race
3947                         DisplayBothClocks();
3948                     }
3949                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3950                     ics_user_moved = 0;
3951                     continue;
3952                 }
3953             }
3954
3955             if (looking_at(buf, &i, "still have time") ||
3956                 looking_at(buf, &i, "not out of time") ||
3957                 looking_at(buf, &i, "either player is out of time") ||
3958                 looking_at(buf, &i, "has timeseal; checking")) {
3959                 /* We must have called his flag a little too soon */
3960                 whiteFlag = blackFlag = FALSE;
3961                 continue;
3962             }
3963
3964             if (looking_at(buf, &i, "added * seconds to") ||
3965                 looking_at(buf, &i, "seconds were added to")) {
3966                 /* Update the clocks */
3967                 SendToICS(ics_prefix);
3968                 SendToICS("refresh\n");
3969                 continue;
3970             }
3971
3972             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3973                 ics_clock_paused = TRUE;
3974                 StopClocks();
3975                 continue;
3976             }
3977
3978             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3979                 ics_clock_paused = FALSE;
3980                 StartClocks();
3981                 continue;
3982             }
3983
3984             /* Grab player ratings from the Creating: message.
3985                Note we have to check for the special case when
3986                the ICS inserts things like [white] or [black]. */
3987             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3988                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3989                 /* star_matches:
3990                    0    player 1 name (not necessarily white)
3991                    1    player 1 rating
3992                    2    empty, white, or black (IGNORED)
3993                    3    player 2 name (not necessarily black)
3994                    4    player 2 rating
3995
3996                    The names/ratings are sorted out when the game
3997                    actually starts (below).
3998                 */
3999                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4000                 player1Rating = string_to_rating(star_match[1]);
4001                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4002                 player2Rating = string_to_rating(star_match[4]);
4003
4004                 if (appData.debugMode)
4005                   fprintf(debugFP,
4006                           "Ratings from 'Creating:' %s %d, %s %d\n",
4007                           player1Name, player1Rating,
4008                           player2Name, player2Rating);
4009
4010                 continue;
4011             }
4012
4013             /* Improved generic start/end-of-game messages */
4014             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4015                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4016                 /* If tkind == 0: */
4017                 /* star_match[0] is the game number */
4018                 /*           [1] is the white player's name */
4019                 /*           [2] is the black player's name */
4020                 /* For end-of-game: */
4021                 /*           [3] is the reason for the game end */
4022                 /*           [4] is a PGN end game-token, preceded by " " */
4023                 /* For start-of-game: */
4024                 /*           [3] begins with "Creating" or "Continuing" */
4025                 /*           [4] is " *" or empty (don't care). */
4026                 int gamenum = atoi(star_match[0]);
4027                 char *whitename, *blackname, *why, *endtoken;
4028                 ChessMove endtype = EndOfFile;
4029
4030                 if (tkind == 0) {
4031                   whitename = star_match[1];
4032                   blackname = star_match[2];
4033                   why = star_match[3];
4034                   endtoken = star_match[4];
4035                 } else {
4036                   whitename = star_match[1];
4037                   blackname = star_match[3];
4038                   why = star_match[5];
4039                   endtoken = star_match[6];
4040                 }
4041
4042                 /* Game start messages */
4043                 if (strncmp(why, "Creating ", 9) == 0 ||
4044                     strncmp(why, "Continuing ", 11) == 0) {
4045                     gs_gamenum = gamenum;
4046                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4047                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4048                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4049 #if ZIPPY
4050                     if (appData.zippyPlay) {
4051                         ZippyGameStart(whitename, blackname);
4052                     }
4053 #endif /*ZIPPY*/
4054                     partnerBoardValid = FALSE; // [HGM] bughouse
4055                     continue;
4056                 }
4057
4058                 /* Game end messages */
4059                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4060                     ics_gamenum != gamenum) {
4061                     continue;
4062                 }
4063                 while (endtoken[0] == ' ') endtoken++;
4064                 switch (endtoken[0]) {
4065                   case '*':
4066                   default:
4067                     endtype = GameUnfinished;
4068                     break;
4069                   case '0':
4070                     endtype = BlackWins;
4071                     break;
4072                   case '1':
4073                     if (endtoken[1] == '/')
4074                       endtype = GameIsDrawn;
4075                     else
4076                       endtype = WhiteWins;
4077                     break;
4078                 }
4079                 GameEnds(endtype, why, GE_ICS);
4080 #if ZIPPY
4081                 if (appData.zippyPlay && first.initDone) {
4082                     ZippyGameEnd(endtype, why);
4083                     if (first.pr == NoProc) {
4084                       /* Start the next process early so that we'll
4085                          be ready for the next challenge */
4086                       StartChessProgram(&first);
4087                     }
4088                     /* Send "new" early, in case this command takes
4089                        a long time to finish, so that we'll be ready
4090                        for the next challenge. */
4091                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4092                     Reset(TRUE, TRUE);
4093                 }
4094 #endif /*ZIPPY*/
4095                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4096                 continue;
4097             }
4098
4099             if (looking_at(buf, &i, "Removing game * from observation") ||
4100                 looking_at(buf, &i, "no longer observing game *") ||
4101                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4102                 if (gameMode == IcsObserving &&
4103                     atoi(star_match[0]) == ics_gamenum)
4104                   {
4105                       /* icsEngineAnalyze */
4106                       if (appData.icsEngineAnalyze) {
4107                             ExitAnalyzeMode();
4108                             ModeHighlight();
4109                       }
4110                       StopClocks();
4111                       gameMode = IcsIdle;
4112                       ics_gamenum = -1;
4113                       ics_user_moved = FALSE;
4114                   }
4115                 continue;
4116             }
4117
4118             if (looking_at(buf, &i, "no longer examining game *")) {
4119                 if (gameMode == IcsExamining &&
4120                     atoi(star_match[0]) == ics_gamenum)
4121                   {
4122                       gameMode = IcsIdle;
4123                       ics_gamenum = -1;
4124                       ics_user_moved = FALSE;
4125                   }
4126                 continue;
4127             }
4128
4129             /* Advance leftover_start past any newlines we find,
4130                so only partial lines can get reparsed */
4131             if (looking_at(buf, &i, "\n")) {
4132                 prevColor = curColor;
4133                 if (curColor != ColorNormal) {
4134                     if (oldi > next_out) {
4135                         SendToPlayer(&buf[next_out], oldi - next_out);
4136                         next_out = oldi;
4137                     }
4138                     Colorize(ColorNormal, FALSE);
4139                     curColor = ColorNormal;
4140                 }
4141                 if (started == STARTED_BOARD) {
4142                     started = STARTED_NONE;
4143                     parse[parse_pos] = NULLCHAR;
4144                     ParseBoard12(parse);
4145                     ics_user_moved = 0;
4146
4147                     /* Send premove here */
4148                     if (appData.premove) {
4149                       char str[MSG_SIZ];
4150                       if (currentMove == 0 &&
4151                           gameMode == IcsPlayingWhite &&
4152                           appData.premoveWhite) {
4153                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4154                         if (appData.debugMode)
4155                           fprintf(debugFP, "Sending premove:\n");
4156                         SendToICS(str);
4157                       } else if (currentMove == 1 &&
4158                                  gameMode == IcsPlayingBlack &&
4159                                  appData.premoveBlack) {
4160                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4161                         if (appData.debugMode)
4162                           fprintf(debugFP, "Sending premove:\n");
4163                         SendToICS(str);
4164                       } else if (gotPremove) {
4165                         gotPremove = 0;
4166                         ClearPremoveHighlights();
4167                         if (appData.debugMode)
4168                           fprintf(debugFP, "Sending premove:\n");
4169                           UserMoveEvent(premoveFromX, premoveFromY,
4170                                         premoveToX, premoveToY,
4171                                         premovePromoChar);
4172                       }
4173                     }
4174
4175                     /* Usually suppress following prompt */
4176                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4177                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4178                         if (looking_at(buf, &i, "*% ")) {
4179                             savingComment = FALSE;
4180                             suppressKibitz = 0;
4181                         }
4182                     }
4183                     next_out = i;
4184                 } else if (started == STARTED_HOLDINGS) {
4185                     int gamenum;
4186                     char new_piece[MSG_SIZ];
4187                     started = STARTED_NONE;
4188                     parse[parse_pos] = NULLCHAR;
4189                     if (appData.debugMode)
4190                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4191                                                         parse, currentMove);
4192                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4193                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4194                         if (gameInfo.variant == VariantNormal) {
4195                           /* [HGM] We seem to switch variant during a game!
4196                            * Presumably no holdings were displayed, so we have
4197                            * to move the position two files to the right to
4198                            * create room for them!
4199                            */
4200                           VariantClass newVariant;
4201                           switch(gameInfo.boardWidth) { // base guess on board width
4202                                 case 9:  newVariant = VariantShogi; break;
4203                                 case 10: newVariant = VariantGreat; break;
4204                                 default: newVariant = VariantCrazyhouse; break;
4205                           }
4206                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4207                           /* Get a move list just to see the header, which
4208                              will tell us whether this is really bug or zh */
4209                           if (ics_getting_history == H_FALSE) {
4210                             ics_getting_history = H_REQUESTED;
4211                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4212                             SendToICS(str);
4213                           }
4214                         }
4215                         new_piece[0] = NULLCHAR;
4216                         sscanf(parse, "game %d white [%s black [%s <- %s",
4217                                &gamenum, white_holding, black_holding,
4218                                new_piece);
4219                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4220                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4221                         /* [HGM] copy holdings to board holdings area */
4222                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4223                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4224                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4225 #if ZIPPY
4226                         if (appData.zippyPlay && first.initDone) {
4227                             ZippyHoldings(white_holding, black_holding,
4228                                           new_piece);
4229                         }
4230 #endif /*ZIPPY*/
4231                         if (tinyLayout || smallLayout) {
4232                             char wh[16], bh[16];
4233                             PackHolding(wh, white_holding);
4234                             PackHolding(bh, black_holding);
4235                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4236                                     gameInfo.white, gameInfo.black);
4237                         } else {
4238                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4239                                     gameInfo.white, white_holding, _("vs."),
4240                                     gameInfo.black, black_holding);
4241                         }
4242                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4243                         DrawPosition(FALSE, boards[currentMove]);
4244                         DisplayTitle(str);
4245                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4246                         sscanf(parse, "game %d white [%s black [%s <- %s",
4247                                &gamenum, white_holding, black_holding,
4248                                new_piece);
4249                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4250                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4251                         /* [HGM] copy holdings to partner-board holdings area */
4252                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4253                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4254                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4255                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4256                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4257                       }
4258                     }
4259                     /* Suppress following prompt */
4260                     if (looking_at(buf, &i, "*% ")) {
4261                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4262                         savingComment = FALSE;
4263                         suppressKibitz = 0;
4264                     }
4265                     next_out = i;
4266                 }
4267                 continue;
4268             }
4269
4270             i++;                /* skip unparsed character and loop back */
4271         }
4272
4273         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4274 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4275 //          SendToPlayer(&buf[next_out], i - next_out);
4276             started != STARTED_HOLDINGS && leftover_start > next_out) {
4277             SendToPlayer(&buf[next_out], leftover_start - next_out);
4278             next_out = i;
4279         }
4280
4281         leftover_len = buf_len - leftover_start;
4282         /* if buffer ends with something we couldn't parse,
4283            reparse it after appending the next read */
4284
4285     } else if (count == 0) {
4286         RemoveInputSource(isr);
4287         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4288     } else {
4289         DisplayFatalError(_("Error reading from ICS"), error, 1);
4290     }
4291 }
4292
4293
4294 /* Board style 12 looks like this:
4295
4296    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4297
4298  * The "<12> " is stripped before it gets to this routine.  The two
4299  * trailing 0's (flip state and clock ticking) are later addition, and
4300  * some chess servers may not have them, or may have only the first.
4301  * Additional trailing fields may be added in the future.
4302  */
4303
4304 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4305
4306 #define RELATION_OBSERVING_PLAYED    0
4307 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4308 #define RELATION_PLAYING_MYMOVE      1
4309 #define RELATION_PLAYING_NOTMYMOVE  -1
4310 #define RELATION_EXAMINING           2
4311 #define RELATION_ISOLATED_BOARD     -3
4312 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4313
4314 void
4315 ParseBoard12 (char *string)
4316 {
4317 #if ZIPPY
4318     int i, takeback;
4319     char *bookHit = NULL; // [HGM] book
4320 #endif
4321     GameMode newGameMode;
4322     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4323     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4324     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4325     char to_play, board_chars[200];
4326     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4327     char black[32], white[32];
4328     Board board;
4329     int prevMove = currentMove;
4330     int ticking = 2;
4331     ChessMove moveType;
4332     int fromX, fromY, toX, toY;
4333     char promoChar;
4334     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4335     Boolean weird = FALSE, reqFlag = FALSE;
4336
4337     fromX = fromY = toX = toY = -1;
4338
4339     newGame = FALSE;
4340
4341     if (appData.debugMode)
4342       fprintf(debugFP, "Parsing board: %s\n", string);
4343
4344     move_str[0] = NULLCHAR;
4345     elapsed_time[0] = NULLCHAR;
4346     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4347         int  i = 0, j;
4348         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4349             if(string[i] == ' ') { ranks++; files = 0; }
4350             else files++;
4351             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4352             i++;
4353         }
4354         for(j = 0; j <i; j++) board_chars[j] = string[j];
4355         board_chars[i] = '\0';
4356         string += i + 1;
4357     }
4358     n = sscanf(string, PATTERN, &to_play, &double_push,
4359                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4360                &gamenum, white, black, &relation, &basetime, &increment,
4361                &white_stren, &black_stren, &white_time, &black_time,
4362                &moveNum, str, elapsed_time, move_str, &ics_flip,
4363                &ticking);
4364
4365     if (n < 21) {
4366         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4367         DisplayError(str, 0);
4368         return;
4369     }
4370
4371     /* Convert the move number to internal form */
4372     moveNum = (moveNum - 1) * 2;
4373     if (to_play == 'B') moveNum++;
4374     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4375       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4376                         0, 1);
4377       return;
4378     }
4379
4380     switch (relation) {
4381       case RELATION_OBSERVING_PLAYED:
4382       case RELATION_OBSERVING_STATIC:
4383         if (gamenum == -1) {
4384             /* Old ICC buglet */
4385             relation = RELATION_OBSERVING_STATIC;
4386         }
4387         newGameMode = IcsObserving;
4388         break;
4389       case RELATION_PLAYING_MYMOVE:
4390       case RELATION_PLAYING_NOTMYMOVE:
4391         newGameMode =
4392           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4393             IcsPlayingWhite : IcsPlayingBlack;
4394         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4395         break;
4396       case RELATION_EXAMINING:
4397         newGameMode = IcsExamining;
4398         break;
4399       case RELATION_ISOLATED_BOARD:
4400       default:
4401         /* Just display this board.  If user was doing something else,
4402            we will forget about it until the next board comes. */
4403         newGameMode = IcsIdle;
4404         break;
4405       case RELATION_STARTING_POSITION:
4406         newGameMode = gameMode;
4407         break;
4408     }
4409
4410     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4411         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4412          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4413       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4414       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4415       static int lastBgGame = -1;
4416       char *toSqr;
4417       for (k = 0; k < ranks; k++) {
4418         for (j = 0; j < files; j++)
4419           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4420         if(gameInfo.holdingsWidth > 1) {
4421              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4422              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4423         }
4424       }
4425       CopyBoard(partnerBoard, board);
4426       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4427         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4428         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4429       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4430       if(toSqr = strchr(str, '-')) {
4431         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4432         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4433       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4434       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4435       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4436       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4437       if(twoBoards) {
4438           DisplayWhiteClock(white_time*fac, to_play == 'W');
4439           DisplayBlackClock(black_time*fac, to_play != 'W');
4440           activePartner = to_play;
4441           if(gamenum != lastBgGame) {
4442               char buf[MSG_SIZ];
4443               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4444               DisplayTitle(buf);
4445           }
4446           lastBgGame = gamenum;
4447           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4448                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4449       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4450                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4451       if(!twoBoards) DisplayMessage(partnerStatus, "");
4452         partnerBoardValid = TRUE;
4453       return;
4454     }
4455
4456     if(appData.dualBoard && appData.bgObserve) {
4457         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4458             SendToICS(ics_prefix), SendToICS("pobserve\n");
4459         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4460             char buf[MSG_SIZ];
4461             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4462             SendToICS(buf);
4463         }
4464     }
4465
4466     /* Modify behavior for initial board display on move listing
4467        of wild games.
4468        */
4469     switch (ics_getting_history) {
4470       case H_FALSE:
4471       case H_REQUESTED:
4472         break;
4473       case H_GOT_REQ_HEADER:
4474       case H_GOT_UNREQ_HEADER:
4475         /* This is the initial position of the current game */
4476         gamenum = ics_gamenum;
4477         moveNum = 0;            /* old ICS bug workaround */
4478         if (to_play == 'B') {
4479           startedFromSetupPosition = TRUE;
4480           blackPlaysFirst = TRUE;
4481           moveNum = 1;
4482           if (forwardMostMove == 0) forwardMostMove = 1;
4483           if (backwardMostMove == 0) backwardMostMove = 1;
4484           if (currentMove == 0) currentMove = 1;
4485         }
4486         newGameMode = gameMode;
4487         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4488         break;
4489       case H_GOT_UNWANTED_HEADER:
4490         /* This is an initial board that we don't want */
4491         return;
4492       case H_GETTING_MOVES:
4493         /* Should not happen */
4494         DisplayError(_("Error gathering move list: extra board"), 0);
4495         ics_getting_history = H_FALSE;
4496         return;
4497     }
4498
4499    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4500                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4501                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4502      /* [HGM] We seem to have switched variant unexpectedly
4503       * Try to guess new variant from board size
4504       */
4505           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4506           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4507           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4508           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4509           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4510           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4511           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4512           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4513           /* Get a move list just to see the header, which
4514              will tell us whether this is really bug or zh */
4515           if (ics_getting_history == H_FALSE) {
4516             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4517             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4518             SendToICS(str);
4519           }
4520     }
4521
4522     /* Take action if this is the first board of a new game, or of a
4523        different game than is currently being displayed.  */
4524     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4525         relation == RELATION_ISOLATED_BOARD) {
4526
4527         /* Forget the old game and get the history (if any) of the new one */
4528         if (gameMode != BeginningOfGame) {
4529           Reset(TRUE, TRUE);
4530         }
4531         newGame = TRUE;
4532         if (appData.autoRaiseBoard) BoardToTop();
4533         prevMove = -3;
4534         if (gamenum == -1) {
4535             newGameMode = IcsIdle;
4536         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4537                    appData.getMoveList && !reqFlag) {
4538             /* Need to get game history */
4539             ics_getting_history = H_REQUESTED;
4540             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4541             SendToICS(str);
4542         }
4543
4544         /* Initially flip the board to have black on the bottom if playing
4545            black or if the ICS flip flag is set, but let the user change
4546            it with the Flip View button. */
4547         flipView = appData.autoFlipView ?
4548           (newGameMode == IcsPlayingBlack) || ics_flip :
4549           appData.flipView;
4550
4551         /* Done with values from previous mode; copy in new ones */
4552         gameMode = newGameMode;
4553         ModeHighlight();
4554         ics_gamenum = gamenum;
4555         if (gamenum == gs_gamenum) {
4556             int klen = strlen(gs_kind);
4557             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4558             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4559             gameInfo.event = StrSave(str);
4560         } else {
4561             gameInfo.event = StrSave("ICS game");
4562         }
4563         gameInfo.site = StrSave(appData.icsHost);
4564         gameInfo.date = PGNDate();
4565         gameInfo.round = StrSave("-");
4566         gameInfo.white = StrSave(white);
4567         gameInfo.black = StrSave(black);
4568         timeControl = basetime * 60 * 1000;
4569         timeControl_2 = 0;
4570         timeIncrement = increment * 1000;
4571         movesPerSession = 0;
4572         gameInfo.timeControl = TimeControlTagValue();
4573         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4574   if (appData.debugMode) {
4575     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4576     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4577     setbuf(debugFP, NULL);
4578   }
4579
4580         gameInfo.outOfBook = NULL;
4581
4582         /* Do we have the ratings? */
4583         if (strcmp(player1Name, white) == 0 &&
4584             strcmp(player2Name, black) == 0) {
4585             if (appData.debugMode)
4586               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4587                       player1Rating, player2Rating);
4588             gameInfo.whiteRating = player1Rating;
4589             gameInfo.blackRating = player2Rating;
4590         } else if (strcmp(player2Name, white) == 0 &&
4591                    strcmp(player1Name, black) == 0) {
4592             if (appData.debugMode)
4593               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4594                       player2Rating, player1Rating);
4595             gameInfo.whiteRating = player2Rating;
4596             gameInfo.blackRating = player1Rating;
4597         }
4598         player1Name[0] = player2Name[0] = NULLCHAR;
4599
4600         /* Silence shouts if requested */
4601         if (appData.quietPlay &&
4602             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4603             SendToICS(ics_prefix);
4604             SendToICS("set shout 0\n");
4605         }
4606     }
4607
4608     /* Deal with midgame name changes */
4609     if (!newGame) {
4610         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4611             if (gameInfo.white) free(gameInfo.white);
4612             gameInfo.white = StrSave(white);
4613         }
4614         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4615             if (gameInfo.black) free(gameInfo.black);
4616             gameInfo.black = StrSave(black);
4617         }
4618     }
4619
4620     /* Throw away game result if anything actually changes in examine mode */
4621     if (gameMode == IcsExamining && !newGame) {
4622         gameInfo.result = GameUnfinished;
4623         if (gameInfo.resultDetails != NULL) {
4624             free(gameInfo.resultDetails);
4625             gameInfo.resultDetails = NULL;
4626         }
4627     }
4628
4629     /* In pausing && IcsExamining mode, we ignore boards coming
4630        in if they are in a different variation than we are. */
4631     if (pauseExamInvalid) return;
4632     if (pausing && gameMode == IcsExamining) {
4633         if (moveNum <= pauseExamForwardMostMove) {
4634             pauseExamInvalid = TRUE;
4635             forwardMostMove = pauseExamForwardMostMove;
4636             return;
4637         }
4638     }
4639
4640   if (appData.debugMode) {
4641     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4642   }
4643     /* Parse the board */
4644     for (k = 0; k < ranks; k++) {
4645       for (j = 0; j < files; j++)
4646         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4647       if(gameInfo.holdingsWidth > 1) {
4648            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4649            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4650       }
4651     }
4652     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4653       board[5][BOARD_RGHT+1] = WhiteAngel;
4654       board[6][BOARD_RGHT+1] = WhiteMarshall;
4655       board[1][0] = BlackMarshall;
4656       board[2][0] = BlackAngel;
4657       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4658     }
4659     CopyBoard(boards[moveNum], board);
4660     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4661     if (moveNum == 0) {
4662         startedFromSetupPosition =
4663           !CompareBoards(board, initialPosition);
4664         if(startedFromSetupPosition)
4665             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4666     }
4667
4668     /* [HGM] Set castling rights. Take the outermost Rooks,
4669        to make it also work for FRC opening positions. Note that board12
4670        is really defective for later FRC positions, as it has no way to
4671        indicate which Rook can castle if they are on the same side of King.
4672        For the initial position we grant rights to the outermost Rooks,
4673        and remember thos rights, and we then copy them on positions
4674        later in an FRC game. This means WB might not recognize castlings with
4675        Rooks that have moved back to their original position as illegal,
4676        but in ICS mode that is not its job anyway.
4677     */
4678     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4679     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4680
4681         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4682             if(board[0][i] == WhiteRook) j = i;
4683         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4684         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4685             if(board[0][i] == WhiteRook) j = i;
4686         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4687         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4688             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4689         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4690         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4691             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4692         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4693
4694         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4695         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4696         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4697             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4698         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4699             if(board[BOARD_HEIGHT-1][k] == bKing)
4700                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4701         if(gameInfo.variant == VariantTwoKings) {
4702             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4703             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4704             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4705         }
4706     } else { int r;
4707         r = boards[moveNum][CASTLING][0] = initialRights[0];
4708         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4709         r = boards[moveNum][CASTLING][1] = initialRights[1];
4710         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4711         r = boards[moveNum][CASTLING][3] = initialRights[3];
4712         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4713         r = boards[moveNum][CASTLING][4] = initialRights[4];
4714         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4715         /* wildcastle kludge: always assume King has rights */
4716         r = boards[moveNum][CASTLING][2] = initialRights[2];
4717         r = boards[moveNum][CASTLING][5] = initialRights[5];
4718     }
4719     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4720     boards[moveNum][EP_STATUS] = EP_NONE;
4721     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4722     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4723     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4724
4725
4726     if (ics_getting_history == H_GOT_REQ_HEADER ||
4727         ics_getting_history == H_GOT_UNREQ_HEADER) {
4728         /* This was an initial position from a move list, not
4729            the current position */
4730         return;
4731     }
4732
4733     /* Update currentMove and known move number limits */
4734     newMove = newGame || moveNum > forwardMostMove;
4735
4736     if (newGame) {
4737         forwardMostMove = backwardMostMove = currentMove = moveNum;
4738         if (gameMode == IcsExamining && moveNum == 0) {
4739           /* Workaround for ICS limitation: we are not told the wild
4740              type when starting to examine a game.  But if we ask for
4741              the move list, the move list header will tell us */
4742             ics_getting_history = H_REQUESTED;
4743             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4744             SendToICS(str);
4745         }
4746     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4747                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4748 #if ZIPPY
4749         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4750         /* [HGM] applied this also to an engine that is silently watching        */
4751         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4752             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4753             gameInfo.variant == currentlyInitializedVariant) {
4754           takeback = forwardMostMove - moveNum;
4755           for (i = 0; i < takeback; i++) {
4756             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4757             SendToProgram("undo\n", &first);
4758           }
4759         }
4760 #endif
4761
4762         forwardMostMove = moveNum;
4763         if (!pausing || currentMove > forwardMostMove)
4764           currentMove = forwardMostMove;
4765     } else {
4766         /* New part of history that is not contiguous with old part */
4767         if (pausing && gameMode == IcsExamining) {
4768             pauseExamInvalid = TRUE;
4769             forwardMostMove = pauseExamForwardMostMove;
4770             return;
4771         }
4772         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4773 #if ZIPPY
4774             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4775                 // [HGM] when we will receive the move list we now request, it will be
4776                 // fed to the engine from the first move on. So if the engine is not
4777                 // in the initial position now, bring it there.
4778                 InitChessProgram(&first, 0);
4779             }
4780 #endif
4781             ics_getting_history = H_REQUESTED;
4782             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4783             SendToICS(str);
4784         }
4785         forwardMostMove = backwardMostMove = currentMove = moveNum;
4786     }
4787
4788     /* Update the clocks */
4789     if (strchr(elapsed_time, '.')) {
4790       /* Time is in ms */
4791       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4792       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4793     } else {
4794       /* Time is in seconds */
4795       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4796       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4797     }
4798
4799
4800 #if ZIPPY
4801     if (appData.zippyPlay && newGame &&
4802         gameMode != IcsObserving && gameMode != IcsIdle &&
4803         gameMode != IcsExamining)
4804       ZippyFirstBoard(moveNum, basetime, increment);
4805 #endif
4806
4807     /* Put the move on the move list, first converting
4808        to canonical algebraic form. */
4809     if (moveNum > 0) {
4810   if (appData.debugMode) {
4811     int f = forwardMostMove;
4812     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4813             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4814             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4815     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4816     fprintf(debugFP, "moveNum = %d\n", moveNum);
4817     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4818     setbuf(debugFP, NULL);
4819   }
4820         if (moveNum <= backwardMostMove) {
4821             /* We don't know what the board looked like before
4822                this move.  Punt. */
4823           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4824             strcat(parseList[moveNum - 1], " ");
4825             strcat(parseList[moveNum - 1], elapsed_time);
4826             moveList[moveNum - 1][0] = NULLCHAR;
4827         } else if (strcmp(move_str, "none") == 0) {
4828             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4829             /* Again, we don't know what the board looked like;
4830                this is really the start of the game. */
4831             parseList[moveNum - 1][0] = NULLCHAR;
4832             moveList[moveNum - 1][0] = NULLCHAR;
4833             backwardMostMove = moveNum;
4834             startedFromSetupPosition = TRUE;
4835             fromX = fromY = toX = toY = -1;
4836         } else {
4837           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4838           //                 So we parse the long-algebraic move string in stead of the SAN move
4839           int valid; char buf[MSG_SIZ], *prom;
4840
4841           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4842                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4843           // str looks something like "Q/a1-a2"; kill the slash
4844           if(str[1] == '/')
4845             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4846           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4847           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4848                 strcat(buf, prom); // long move lacks promo specification!
4849           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4850                 if(appData.debugMode)
4851                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4852                 safeStrCpy(move_str, buf, MSG_SIZ);
4853           }
4854           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4855                                 &fromX, &fromY, &toX, &toY, &promoChar)
4856                || ParseOneMove(buf, moveNum - 1, &moveType,
4857                                 &fromX, &fromY, &toX, &toY, &promoChar);
4858           // end of long SAN patch
4859           if (valid) {
4860             (void) CoordsToAlgebraic(boards[moveNum - 1],
4861                                      PosFlags(moveNum - 1),
4862                                      fromY, fromX, toY, toX, promoChar,
4863                                      parseList[moveNum-1]);
4864             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4865               case MT_NONE:
4866               case MT_STALEMATE:
4867               default:
4868                 break;
4869               case MT_CHECK:
4870                 if(!IS_SHOGI(gameInfo.variant))
4871                     strcat(parseList[moveNum - 1], "+");
4872                 break;
4873               case MT_CHECKMATE:
4874               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4875                 strcat(parseList[moveNum - 1], "#");
4876                 break;
4877             }
4878             strcat(parseList[moveNum - 1], " ");
4879             strcat(parseList[moveNum - 1], elapsed_time);
4880             /* currentMoveString is set as a side-effect of ParseOneMove */
4881             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4882             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4883             strcat(moveList[moveNum - 1], "\n");
4884
4885             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4886                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4887               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4888                 ChessSquare old, new = boards[moveNum][k][j];
4889                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4890                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4891                   if(old == new) continue;
4892                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4893                   else if(new == WhiteWazir || new == BlackWazir) {
4894                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4895                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4896                       else boards[moveNum][k][j] = old; // preserve type of Gold
4897                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4898                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4899               }
4900           } else {
4901             /* Move from ICS was illegal!?  Punt. */
4902             if (appData.debugMode) {
4903               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4904               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4905             }
4906             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4907             strcat(parseList[moveNum - 1], " ");
4908             strcat(parseList[moveNum - 1], elapsed_time);
4909             moveList[moveNum - 1][0] = NULLCHAR;
4910             fromX = fromY = toX = toY = -1;
4911           }
4912         }
4913   if (appData.debugMode) {
4914     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4915     setbuf(debugFP, NULL);
4916   }
4917
4918 #if ZIPPY
4919         /* Send move to chess program (BEFORE animating it). */
4920         if (appData.zippyPlay && !newGame && newMove &&
4921            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4922
4923             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4924                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4925                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4926                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4927                             move_str);
4928                     DisplayError(str, 0);
4929                 } else {
4930                     if (first.sendTime) {
4931                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4932                     }
4933                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4934                     if (firstMove && !bookHit) {
4935                         firstMove = FALSE;
4936                         if (first.useColors) {
4937                           SendToProgram(gameMode == IcsPlayingWhite ?
4938                                         "white\ngo\n" :
4939                                         "black\ngo\n", &first);
4940                         } else {
4941                           SendToProgram("go\n", &first);
4942                         }
4943                         first.maybeThinking = TRUE;
4944                     }
4945                 }
4946             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4947               if (moveList[moveNum - 1][0] == NULLCHAR) {
4948                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4949                 DisplayError(str, 0);
4950               } else {
4951                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4952                 SendMoveToProgram(moveNum - 1, &first);
4953               }
4954             }
4955         }
4956 #endif
4957     }
4958
4959     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4960         /* If move comes from a remote source, animate it.  If it
4961            isn't remote, it will have already been animated. */
4962         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4963             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4964         }
4965         if (!pausing && appData.highlightLastMove) {
4966             SetHighlights(fromX, fromY, toX, toY);
4967         }
4968     }
4969
4970     /* Start the clocks */
4971     whiteFlag = blackFlag = FALSE;
4972     appData.clockMode = !(basetime == 0 && increment == 0);
4973     if (ticking == 0) {
4974       ics_clock_paused = TRUE;
4975       StopClocks();
4976     } else if (ticking == 1) {
4977       ics_clock_paused = FALSE;
4978     }
4979     if (gameMode == IcsIdle ||
4980         relation == RELATION_OBSERVING_STATIC ||
4981         relation == RELATION_EXAMINING ||
4982         ics_clock_paused)
4983       DisplayBothClocks();
4984     else
4985       StartClocks();
4986
4987     /* Display opponents and material strengths */
4988     if (gameInfo.variant != VariantBughouse &&
4989         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4990         if (tinyLayout || smallLayout) {
4991             if(gameInfo.variant == VariantNormal)
4992               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4993                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4994                     basetime, increment);
4995             else
4996               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4997                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4998                     basetime, increment, (int) gameInfo.variant);
4999         } else {
5000             if(gameInfo.variant == VariantNormal)
5001               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5002                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5003                     basetime, increment);
5004             else
5005               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5006                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5007                     basetime, increment, VariantName(gameInfo.variant));
5008         }
5009         DisplayTitle(str);
5010   if (appData.debugMode) {
5011     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5012   }
5013     }
5014
5015
5016     /* Display the board */
5017     if (!pausing && !appData.noGUI) {
5018
5019       if (appData.premove)
5020           if (!gotPremove ||
5021              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5022              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5023               ClearPremoveHighlights();
5024
5025       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5026         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5027       DrawPosition(j, boards[currentMove]);
5028
5029       DisplayMove(moveNum - 1);
5030       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5031             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5032               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5033         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5034       }
5035     }
5036
5037     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5038 #if ZIPPY
5039     if(bookHit) { // [HGM] book: simulate book reply
5040         static char bookMove[MSG_SIZ]; // a bit generous?
5041
5042         programStats.nodes = programStats.depth = programStats.time =
5043         programStats.score = programStats.got_only_move = 0;
5044         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5045
5046         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5047         strcat(bookMove, bookHit);
5048         HandleMachineMove(bookMove, &first);
5049     }
5050 #endif
5051 }
5052
5053 void
5054 GetMoveListEvent ()
5055 {
5056     char buf[MSG_SIZ];
5057     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5058         ics_getting_history = H_REQUESTED;
5059         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5060         SendToICS(buf);
5061     }
5062 }
5063
5064 void
5065 SendToBoth (char *msg)
5066 {   // to make it easy to keep two engines in step in dual analysis
5067     SendToProgram(msg, &first);
5068     if(second.analyzing) SendToProgram(msg, &second);
5069 }
5070
5071 void
5072 AnalysisPeriodicEvent (int force)
5073 {
5074     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5075          && !force) || !appData.periodicUpdates)
5076       return;
5077
5078     /* Send . command to Crafty to collect stats */
5079     SendToBoth(".\n");
5080
5081     /* Don't send another until we get a response (this makes
5082        us stop sending to old Crafty's which don't understand
5083        the "." command (sending illegal cmds resets node count & time,
5084        which looks bad)) */
5085     programStats.ok_to_send = 0;
5086 }
5087
5088 void
5089 ics_update_width (int new_width)
5090 {
5091         ics_printf("set width %d\n", new_width);
5092 }
5093
5094 void
5095 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5096 {
5097     char buf[MSG_SIZ];
5098
5099     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5100         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5101             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5102             SendToProgram(buf, cps);
5103             return;
5104         }
5105         // null move in variant where engine does not understand it (for analysis purposes)
5106         SendBoard(cps, moveNum + 1); // send position after move in stead.
5107         return;
5108     }
5109     if (cps->useUsermove) {
5110       SendToProgram("usermove ", cps);
5111     }
5112     if (cps->useSAN) {
5113       char *space;
5114       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5115         int len = space - parseList[moveNum];
5116         memcpy(buf, parseList[moveNum], len);
5117         buf[len++] = '\n';
5118         buf[len] = NULLCHAR;
5119       } else {
5120         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5121       }
5122       SendToProgram(buf, cps);
5123     } else {
5124       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5125         AlphaRank(moveList[moveNum], 4);
5126         SendToProgram(moveList[moveNum], cps);
5127         AlphaRank(moveList[moveNum], 4); // and back
5128       } else
5129       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5130        * the engine. It would be nice to have a better way to identify castle
5131        * moves here. */
5132       if(appData.fischerCastling && cps->useOOCastle) {
5133         int fromX = moveList[moveNum][0] - AAA;
5134         int fromY = moveList[moveNum][1] - ONE;
5135         int toX = moveList[moveNum][2] - AAA;
5136         int toY = moveList[moveNum][3] - ONE;
5137         if((boards[moveNum][fromY][fromX] == WhiteKing
5138             && boards[moveNum][toY][toX] == WhiteRook)
5139            || (boards[moveNum][fromY][fromX] == BlackKing
5140                && boards[moveNum][toY][toX] == BlackRook)) {
5141           if(toX > fromX) SendToProgram("O-O\n", cps);
5142           else SendToProgram("O-O-O\n", cps);
5143         }
5144         else SendToProgram(moveList[moveNum], cps);
5145       } else
5146       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5147         char *m = moveList[moveNum];
5148         if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
5149           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5150                                                m[2], m[3] - '0',
5151                                                m[5], m[6] - '0',
5152                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5153         else
5154           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5155                                                m[5], m[6] - '0',
5156                                                m[5], m[6] - '0',
5157                                                m[2], m[3] - '0');
5158           SendToProgram(buf, cps);
5159       } else
5160       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5161         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5162           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5163           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5164                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5165         } else
5166           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5167                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5168         SendToProgram(buf, cps);
5169       }
5170       else SendToProgram(moveList[moveNum], cps);
5171       /* End of additions by Tord */
5172     }
5173
5174     /* [HGM] setting up the opening has brought engine in force mode! */
5175     /*       Send 'go' if we are in a mode where machine should play. */
5176     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5177         (gameMode == TwoMachinesPlay   ||
5178 #if ZIPPY
5179          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5180 #endif
5181          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5182         SendToProgram("go\n", cps);
5183   if (appData.debugMode) {
5184     fprintf(debugFP, "(extra)\n");
5185   }
5186     }
5187     setboardSpoiledMachineBlack = 0;
5188 }
5189
5190 void
5191 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5192 {
5193     char user_move[MSG_SIZ];
5194     char suffix[4];
5195
5196     if(gameInfo.variant == VariantSChess && promoChar) {
5197         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5198         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5199     } else suffix[0] = NULLCHAR;
5200
5201     switch (moveType) {
5202       default:
5203         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5204                 (int)moveType, fromX, fromY, toX, toY);
5205         DisplayError(user_move + strlen("say "), 0);
5206         break;
5207       case WhiteKingSideCastle:
5208       case BlackKingSideCastle:
5209       case WhiteQueenSideCastleWild:
5210       case BlackQueenSideCastleWild:
5211       /* PUSH Fabien */
5212       case WhiteHSideCastleFR:
5213       case BlackHSideCastleFR:
5214       /* POP Fabien */
5215         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5216         break;
5217       case WhiteQueenSideCastle:
5218       case BlackQueenSideCastle:
5219       case WhiteKingSideCastleWild:
5220       case BlackKingSideCastleWild:
5221       /* PUSH Fabien */
5222       case WhiteASideCastleFR:
5223       case BlackASideCastleFR:
5224       /* POP Fabien */
5225         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5226         break;
5227       case WhiteNonPromotion:
5228       case BlackNonPromotion:
5229         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5230         break;
5231       case WhitePromotion:
5232       case BlackPromotion:
5233         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5234            gameInfo.variant == VariantMakruk)
5235           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5236                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5237                 PieceToChar(WhiteFerz));
5238         else if(gameInfo.variant == VariantGreat)
5239           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5240                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5241                 PieceToChar(WhiteMan));
5242         else
5243           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5244                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5245                 promoChar);
5246         break;
5247       case WhiteDrop:
5248       case BlackDrop:
5249       drop:
5250         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5251                  ToUpper(PieceToChar((ChessSquare) fromX)),
5252                  AAA + toX, ONE + toY);
5253         break;
5254       case IllegalMove:  /* could be a variant we don't quite understand */
5255         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5256       case NormalMove:
5257       case WhiteCapturesEnPassant:
5258       case BlackCapturesEnPassant:
5259         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5260                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5261         break;
5262     }
5263     SendToICS(user_move);
5264     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5265         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5266 }
5267
5268 void
5269 UploadGameEvent ()
5270 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5271     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5272     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5273     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5274       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5275       return;
5276     }
5277     if(gameMode != IcsExamining) { // is this ever not the case?
5278         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5279
5280         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5281           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5282         } else { // on FICS we must first go to general examine mode
5283           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5284         }
5285         if(gameInfo.variant != VariantNormal) {
5286             // try figure out wild number, as xboard names are not always valid on ICS
5287             for(i=1; i<=36; i++) {
5288               snprintf(buf, MSG_SIZ, "wild/%d", i);
5289                 if(StringToVariant(buf) == gameInfo.variant) break;
5290             }
5291             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5292             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5293             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5294         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5295         SendToICS(ics_prefix);
5296         SendToICS(buf);
5297         if(startedFromSetupPosition || backwardMostMove != 0) {
5298           fen = PositionToFEN(backwardMostMove, NULL, 1);
5299           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5300             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5301             SendToICS(buf);
5302           } else { // FICS: everything has to set by separate bsetup commands
5303             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5304             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5305             SendToICS(buf);
5306             if(!WhiteOnMove(backwardMostMove)) {
5307                 SendToICS("bsetup tomove black\n");
5308             }
5309             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5310             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5311             SendToICS(buf);
5312             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5313             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5314             SendToICS(buf);
5315             i = boards[backwardMostMove][EP_STATUS];
5316             if(i >= 0) { // set e.p.
5317               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5318                 SendToICS(buf);
5319             }
5320             bsetup++;
5321           }
5322         }
5323       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5324             SendToICS("bsetup done\n"); // switch to normal examining.
5325     }
5326     for(i = backwardMostMove; i<last; i++) {
5327         char buf[20];
5328         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5329         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5330             int len = strlen(moveList[i]);
5331             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5332             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5333         }
5334         SendToICS(buf);
5335     }
5336     SendToICS(ics_prefix);
5337     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5338 }
5339
5340 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5341 int legNr = 1;
5342
5343 void
5344 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5345 {
5346     if (rf == DROP_RANK) {
5347       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5348       sprintf(move, "%c@%c%c\n",
5349                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5350     } else {
5351         if (promoChar == 'x' || promoChar == NULLCHAR) {
5352           sprintf(move, "%c%c%c%c\n",
5353                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5354           if(killX >= 0 && killY >= 0) {
5355             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5356             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + killX, ONE + killY);
5357           }
5358         } else {
5359             sprintf(move, "%c%c%c%c%c\n",
5360                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5361         }
5362     }
5363 }
5364
5365 void
5366 ProcessICSInitScript (FILE *f)
5367 {
5368     char buf[MSG_SIZ];
5369
5370     while (fgets(buf, MSG_SIZ, f)) {
5371         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5372     }
5373
5374     fclose(f);
5375 }
5376
5377
5378 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5379 int dragging;
5380 static ClickType lastClickType;
5381
5382 int
5383 PieceInString (char *s, ChessSquare piece)
5384 {
5385   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5386   while((p = strchr(s, ID))) {
5387     if(!suffix || p[1] == suffix) return TRUE;
5388     s = p;
5389   }
5390   return FALSE;
5391 }
5392
5393 int
5394 Partner (ChessSquare *p)
5395 { // change piece into promotion partner if one shogi-promotes to the other
5396   ChessSquare partner = promoPartner[*p];
5397   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5398   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5399   *p = partner;
5400   return 1;
5401 }
5402
5403 void
5404 Sweep (int step)
5405 {
5406     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5407     static int toggleFlag;
5408     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5409     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5410     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5411     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5412     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5413     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5414     do {
5415         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5416         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5417         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5418         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5419         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5420         if(!step) step = -1;
5421     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5422             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5423             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5424             promoSweep == pawn ||
5425             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5426             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5427     if(toX >= 0) {
5428         int victim = boards[currentMove][toY][toX];
5429         boards[currentMove][toY][toX] = promoSweep;
5430         DrawPosition(FALSE, boards[currentMove]);
5431         boards[currentMove][toY][toX] = victim;
5432     } else
5433     ChangeDragPiece(promoSweep);
5434 }
5435
5436 int
5437 PromoScroll (int x, int y)
5438 {
5439   int step = 0;
5440
5441   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5442   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5443   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5444   if(!step) return FALSE;
5445   lastX = x; lastY = y;
5446   if((promoSweep < BlackPawn) == flipView) step = -step;
5447   if(step > 0) selectFlag = 1;
5448   if(!selectFlag) Sweep(step);
5449   return FALSE;
5450 }
5451
5452 void
5453 NextPiece (int step)
5454 {
5455     ChessSquare piece = boards[currentMove][toY][toX];
5456     do {
5457         pieceSweep -= step;
5458         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5459         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5460         if(!step) step = -1;
5461     } while(PieceToChar(pieceSweep) == '.');
5462     boards[currentMove][toY][toX] = pieceSweep;
5463     DrawPosition(FALSE, boards[currentMove]);
5464     boards[currentMove][toY][toX] = piece;
5465 }
5466 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5467 void
5468 AlphaRank (char *move, int n)
5469 {
5470 //    char *p = move, c; int x, y;
5471
5472     if (appData.debugMode) {
5473         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5474     }
5475
5476     if(move[1]=='*' &&
5477        move[2]>='0' && move[2]<='9' &&
5478        move[3]>='a' && move[3]<='x'    ) {
5479         move[1] = '@';
5480         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5481         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5482     } else
5483     if(move[0]>='0' && move[0]<='9' &&
5484        move[1]>='a' && move[1]<='x' &&
5485        move[2]>='0' && move[2]<='9' &&
5486        move[3]>='a' && move[3]<='x'    ) {
5487         /* input move, Shogi -> normal */
5488         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5489         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5490         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5491         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5492     } else
5493     if(move[1]=='@' &&
5494        move[3]>='0' && move[3]<='9' &&
5495        move[2]>='a' && move[2]<='x'    ) {
5496         move[1] = '*';
5497         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5498         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5499     } else
5500     if(
5501        move[0]>='a' && move[0]<='x' &&
5502        move[3]>='0' && move[3]<='9' &&
5503        move[2]>='a' && move[2]<='x'    ) {
5504          /* output move, normal -> Shogi */
5505         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5506         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5507         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5508         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5509         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5510     }
5511     if (appData.debugMode) {
5512         fprintf(debugFP, "   out = '%s'\n", move);
5513     }
5514 }
5515
5516 char yy_textstr[8000];
5517
5518 /* Parser for moves from gnuchess, ICS, or user typein box */
5519 Boolean
5520 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5521 {
5522     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5523
5524     switch (*moveType) {
5525       case WhitePromotion:
5526       case BlackPromotion:
5527       case WhiteNonPromotion:
5528       case BlackNonPromotion:
5529       case NormalMove:
5530       case FirstLeg:
5531       case WhiteCapturesEnPassant:
5532       case BlackCapturesEnPassant:
5533       case WhiteKingSideCastle:
5534       case WhiteQueenSideCastle:
5535       case BlackKingSideCastle:
5536       case BlackQueenSideCastle:
5537       case WhiteKingSideCastleWild:
5538       case WhiteQueenSideCastleWild:
5539       case BlackKingSideCastleWild:
5540       case BlackQueenSideCastleWild:
5541       /* Code added by Tord: */
5542       case WhiteHSideCastleFR:
5543       case WhiteASideCastleFR:
5544       case BlackHSideCastleFR:
5545       case BlackASideCastleFR:
5546       /* End of code added by Tord */
5547       case IllegalMove:         /* bug or odd chess variant */
5548         if(currentMoveString[1] == '@') { // illegal drop
5549           *fromX = WhiteOnMove(moveNum) ?
5550             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5551             (int) CharToPiece(ToLower(currentMoveString[0]));
5552           goto drop;
5553         }
5554         *fromX = currentMoveString[0] - AAA;
5555         *fromY = currentMoveString[1] - ONE;
5556         *toX = currentMoveString[2] - AAA;
5557         *toY = currentMoveString[3] - ONE;
5558         *promoChar = currentMoveString[4];
5559         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5560             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5561     if (appData.debugMode) {
5562         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5563     }
5564             *fromX = *fromY = *toX = *toY = 0;
5565             return FALSE;
5566         }
5567         if (appData.testLegality) {
5568           return (*moveType != IllegalMove);
5569         } else {
5570           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5571                          // [HGM] lion: if this is a double move we are less critical
5572                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5573         }
5574
5575       case WhiteDrop:
5576       case BlackDrop:
5577         *fromX = *moveType == WhiteDrop ?
5578           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5579           (int) CharToPiece(ToLower(currentMoveString[0]));
5580       drop:
5581         *fromY = DROP_RANK;
5582         *toX = currentMoveString[2] - AAA;
5583         *toY = currentMoveString[3] - ONE;
5584         *promoChar = NULLCHAR;
5585         return TRUE;
5586
5587       case AmbiguousMove:
5588       case ImpossibleMove:
5589       case EndOfFile:
5590       case ElapsedTime:
5591       case Comment:
5592       case PGNTag:
5593       case NAG:
5594       case WhiteWins:
5595       case BlackWins:
5596       case GameIsDrawn:
5597       default:
5598     if (appData.debugMode) {
5599         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5600     }
5601         /* bug? */
5602         *fromX = *fromY = *toX = *toY = 0;
5603         *promoChar = NULLCHAR;
5604         return FALSE;
5605     }
5606 }
5607
5608 Boolean pushed = FALSE;
5609 char *lastParseAttempt;
5610
5611 void
5612 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5613 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5614   int fromX, fromY, toX, toY; char promoChar;
5615   ChessMove moveType;
5616   Boolean valid;
5617   int nr = 0;
5618
5619   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5620   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5621     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5622     pushed = TRUE;
5623   }
5624   endPV = forwardMostMove;
5625   do {
5626     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5627     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5628     lastParseAttempt = pv;
5629     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5630     if(!valid && nr == 0 &&
5631        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5632         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5633         // Hande case where played move is different from leading PV move
5634         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5635         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5636         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5637         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5638           endPV += 2; // if position different, keep this
5639           moveList[endPV-1][0] = fromX + AAA;
5640           moveList[endPV-1][1] = fromY + ONE;
5641           moveList[endPV-1][2] = toX + AAA;
5642           moveList[endPV-1][3] = toY + ONE;
5643           parseList[endPV-1][0] = NULLCHAR;
5644           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5645         }
5646       }
5647     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5648     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5649     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5650     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5651         valid++; // allow comments in PV
5652         continue;
5653     }
5654     nr++;
5655     if(endPV+1 > framePtr) break; // no space, truncate
5656     if(!valid) break;
5657     endPV++;
5658     CopyBoard(boards[endPV], boards[endPV-1]);
5659     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5660     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5661     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5662     CoordsToAlgebraic(boards[endPV - 1],
5663                              PosFlags(endPV - 1),
5664                              fromY, fromX, toY, toX, promoChar,
5665                              parseList[endPV - 1]);
5666   } while(valid);
5667   if(atEnd == 2) return; // used hidden, for PV conversion
5668   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5669   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5670   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5671                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5672   DrawPosition(TRUE, boards[currentMove]);
5673 }
5674
5675 int
5676 MultiPV (ChessProgramState *cps, int kind)
5677 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5678         int i;
5679         for(i=0; i<cps->nrOptions; i++) {
5680             char *s = cps->option[i].name;
5681             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5682             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5683                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5684         }
5685         return -1;
5686 }
5687
5688 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5689 static int multi, pv_margin;
5690 static ChessProgramState *activeCps;
5691
5692 Boolean
5693 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5694 {
5695         int startPV, lineStart, origIndex = index;
5696         char *p, buf2[MSG_SIZ];
5697         ChessProgramState *cps = (pane ? &second : &first);
5698
5699         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5700         lastX = x; lastY = y;
5701         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5702         lineStart = startPV = index;
5703         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5704         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5705         index = startPV;
5706         do{ while(buf[index] && buf[index] != '\n') index++;
5707         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5708         buf[index] = 0;
5709         if(lineStart == 0 && gameMode == AnalyzeMode) {
5710             int n = 0;
5711             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5712             if(n == 0) { // click not on "fewer" or "more"
5713                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5714                     pv_margin = cps->option[multi].value;
5715                     activeCps = cps; // non-null signals margin adjustment
5716                 }
5717             } else if((multi = MultiPV(cps, 1)) >= 0) {
5718                 n += cps->option[multi].value; if(n < 1) n = 1;
5719                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5720                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5721                 cps->option[multi].value = n;
5722                 *start = *end = 0;
5723                 return FALSE;
5724             }
5725         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5726                 ExcludeClick(origIndex - lineStart);
5727                 return FALSE;
5728         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5729                 Collapse(origIndex - lineStart);
5730                 return FALSE;
5731         }
5732         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5733         *start = startPV; *end = index-1;
5734         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5735         return TRUE;
5736 }
5737
5738 char *
5739 PvToSAN (char *pv)
5740 {
5741         static char buf[10*MSG_SIZ];
5742         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5743         *buf = NULLCHAR;
5744         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5745         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5746         for(i = forwardMostMove; i<endPV; i++){
5747             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5748             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5749             k += strlen(buf+k);
5750         }
5751         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5752         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5753         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5754         endPV = savedEnd;
5755         return buf;
5756 }
5757
5758 Boolean
5759 LoadPV (int x, int y)
5760 { // called on right mouse click to load PV
5761   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5762   lastX = x; lastY = y;
5763   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5764   extendGame = FALSE;
5765   return TRUE;
5766 }
5767
5768 void
5769 UnLoadPV ()
5770 {
5771   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5772   if(activeCps) {
5773     if(pv_margin != activeCps->option[multi].value) {
5774       char buf[MSG_SIZ];
5775       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5776       SendToProgram(buf, activeCps);
5777       activeCps->option[multi].value = pv_margin;
5778     }
5779     activeCps = NULL;
5780     return;
5781   }
5782   if(endPV < 0) return;
5783   if(appData.autoCopyPV) CopyFENToClipboard();
5784   endPV = -1;
5785   if(extendGame && currentMove > forwardMostMove) {
5786         Boolean saveAnimate = appData.animate;
5787         if(pushed) {
5788             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5789                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5790             } else storedGames--; // abandon shelved tail of original game
5791         }
5792         pushed = FALSE;
5793         forwardMostMove = currentMove;
5794         currentMove = oldFMM;
5795         appData.animate = FALSE;
5796         ToNrEvent(forwardMostMove);
5797         appData.animate = saveAnimate;
5798   }
5799   currentMove = forwardMostMove;
5800   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5801   ClearPremoveHighlights();
5802   DrawPosition(TRUE, boards[currentMove]);
5803 }
5804
5805 void
5806 MovePV (int x, int y, int h)
5807 { // step through PV based on mouse coordinates (called on mouse move)
5808   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5809
5810   if(activeCps) { // adjusting engine's multi-pv margin
5811     if(x > lastX) pv_margin++; else
5812     if(x < lastX) pv_margin -= (pv_margin > 0);
5813     if(x != lastX) {
5814       char buf[MSG_SIZ];
5815       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5816       DisplayMessage(buf, "");
5817     }
5818     lastX = x;
5819     return;
5820   }
5821   // we must somehow check if right button is still down (might be released off board!)
5822   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5823   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5824   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5825   if(!step) return;
5826   lastX = x; lastY = y;
5827
5828   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5829   if(endPV < 0) return;
5830   if(y < margin) step = 1; else
5831   if(y > h - margin) step = -1;
5832   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5833   currentMove += step;
5834   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5835   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5836                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5837   DrawPosition(FALSE, boards[currentMove]);
5838 }
5839
5840
5841 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5842 // All positions will have equal probability, but the current method will not provide a unique
5843 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5844 #define DARK 1
5845 #define LITE 2
5846 #define ANY 3
5847
5848 int squaresLeft[4];
5849 int piecesLeft[(int)BlackPawn];
5850 int seed, nrOfShuffles;
5851
5852 void
5853 GetPositionNumber ()
5854 {       // sets global variable seed
5855         int i;
5856
5857         seed = appData.defaultFrcPosition;
5858         if(seed < 0) { // randomize based on time for negative FRC position numbers
5859                 for(i=0; i<50; i++) seed += random();
5860                 seed = random() ^ random() >> 8 ^ random() << 8;
5861                 if(seed<0) seed = -seed;
5862         }
5863 }
5864
5865 int
5866 put (Board board, int pieceType, int rank, int n, int shade)
5867 // put the piece on the (n-1)-th empty squares of the given shade
5868 {
5869         int i;
5870
5871         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5872                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5873                         board[rank][i] = (ChessSquare) pieceType;
5874                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5875                         squaresLeft[ANY]--;
5876                         piecesLeft[pieceType]--;
5877                         return i;
5878                 }
5879         }
5880         return -1;
5881 }
5882
5883
5884 void
5885 AddOnePiece (Board board, int pieceType, int rank, int shade)
5886 // calculate where the next piece goes, (any empty square), and put it there
5887 {
5888         int i;
5889
5890         i = seed % squaresLeft[shade];
5891         nrOfShuffles *= squaresLeft[shade];
5892         seed /= squaresLeft[shade];
5893         put(board, pieceType, rank, i, shade);
5894 }
5895
5896 void
5897 AddTwoPieces (Board board, int pieceType, int rank)
5898 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5899 {
5900         int i, n=squaresLeft[ANY], j=n-1, k;
5901
5902         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5903         i = seed % k;  // pick one
5904         nrOfShuffles *= k;
5905         seed /= k;
5906         while(i >= j) i -= j--;
5907         j = n - 1 - j; i += j;
5908         put(board, pieceType, rank, j, ANY);
5909         put(board, pieceType, rank, i, ANY);
5910 }
5911
5912 void
5913 SetUpShuffle (Board board, int number)
5914 {
5915         int i, p, first=1;
5916
5917         GetPositionNumber(); nrOfShuffles = 1;
5918
5919         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5920         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5921         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5922
5923         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5924
5925         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5926             p = (int) board[0][i];
5927             if(p < (int) BlackPawn) piecesLeft[p] ++;
5928             board[0][i] = EmptySquare;
5929         }
5930
5931         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5932             // shuffles restricted to allow normal castling put KRR first
5933             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5934                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5935             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5936                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5937             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5938                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5939             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5940                 put(board, WhiteRook, 0, 0, ANY);
5941             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5942         }
5943
5944         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5945             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5946             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5947                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5948                 while(piecesLeft[p] >= 2) {
5949                     AddOnePiece(board, p, 0, LITE);
5950                     AddOnePiece(board, p, 0, DARK);
5951                 }
5952                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5953             }
5954
5955         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5956             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5957             // but we leave King and Rooks for last, to possibly obey FRC restriction
5958             if(p == (int)WhiteRook) continue;
5959             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5960             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5961         }
5962
5963         // now everything is placed, except perhaps King (Unicorn) and Rooks
5964
5965         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5966             // Last King gets castling rights
5967             while(piecesLeft[(int)WhiteUnicorn]) {
5968                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5969                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5970             }
5971
5972             while(piecesLeft[(int)WhiteKing]) {
5973                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5974                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5975             }
5976
5977
5978         } else {
5979             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5980             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5981         }
5982
5983         // Only Rooks can be left; simply place them all
5984         while(piecesLeft[(int)WhiteRook]) {
5985                 i = put(board, WhiteRook, 0, 0, ANY);
5986                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5987                         if(first) {
5988                                 first=0;
5989                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5990                         }
5991                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5992                 }
5993         }
5994         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5995             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5996         }
5997
5998         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5999 }
6000
6001 int
6002 ptclen (const char *s, char *escapes)
6003 {
6004     int n = 0;
6005     if(!*escapes) return strlen(s);
6006     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)), s++;
6007     return n;
6008 }
6009
6010 int
6011 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6012 /* [HGM] moved here from winboard.c because of its general usefulness */
6013 /*       Basically a safe strcpy that uses the last character as King */
6014 {
6015     int result = FALSE; int NrPieces;
6016     unsigned char partner[EmptySquare];
6017
6018     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6019                     && NrPieces >= 12 && !(NrPieces&1)) {
6020         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6021
6022         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6023         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6024             char *p, c=0;
6025             if(map[j] == '/') offs = WhitePBishop - i, j++;
6026             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6027             table[i+offs] = map[j++];
6028             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6029             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6030         }
6031         table[(int) WhiteKing]  = map[j++];
6032         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6033             char *p, c=0;
6034             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6035             i = WHITE_TO_BLACK ii;
6036             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6037             table[i+offs] = map[j++];
6038             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6039             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6040         }
6041         table[(int) BlackKing]  = map[j++];
6042
6043
6044         if(*escapes) { // set up promotion pairing
6045             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6046             // pieceToChar entirely filled, so we can look up specified partners
6047             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6048                 int c = table[i];
6049                 if(c == '^' || c == '-') { // has specified partner
6050                     int p;
6051                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6052                     if(c == '^') table[i] = '+';
6053                     if(p < EmptySquare) {
6054                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6055                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6056                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6057                     }
6058                 } else if(c == '*') {
6059                     table[i] = partner[i];
6060                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6061                 }
6062             }
6063         }
6064
6065         result = TRUE;
6066     }
6067
6068     return result;
6069 }
6070
6071 int
6072 SetCharTable (unsigned char *table, const char * map)
6073 {
6074     return SetCharTableEsc(table, map, "");
6075 }
6076
6077 void
6078 Prelude (Board board)
6079 {       // [HGM] superchess: random selection of exo-pieces
6080         int i, j, k; ChessSquare p;
6081         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6082
6083         GetPositionNumber(); // use FRC position number
6084
6085         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6086             SetCharTable(pieceToChar, appData.pieceToCharTable);
6087             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6088                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6089         }
6090
6091         j = seed%4;                 seed /= 4;
6092         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6093         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6094         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6095         j = seed%3 + (seed%3 >= j); seed /= 3;
6096         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6097         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6098         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6099         j = seed%3;                 seed /= 3;
6100         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6101         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6102         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6103         j = seed%2 + (seed%2 >= j); seed /= 2;
6104         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6105         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6106         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6107         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6108         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6109         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6110         put(board, exoPieces[0],    0, 0, ANY);
6111         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6112 }
6113
6114 void
6115 InitPosition (int redraw)
6116 {
6117     ChessSquare (* pieces)[BOARD_FILES];
6118     int i, j, pawnRow=1, pieceRows=1, overrule,
6119     oldx = gameInfo.boardWidth,
6120     oldy = gameInfo.boardHeight,
6121     oldh = gameInfo.holdingsWidth;
6122     static int oldv;
6123
6124     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6125
6126     /* [AS] Initialize pv info list [HGM] and game status */
6127     {
6128         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6129             pvInfoList[i].depth = 0;
6130             boards[i][EP_STATUS] = EP_NONE;
6131             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6132         }
6133
6134         initialRulePlies = 0; /* 50-move counter start */
6135
6136         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6137         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6138     }
6139
6140
6141     /* [HGM] logic here is completely changed. In stead of full positions */
6142     /* the initialized data only consist of the two backranks. The switch */
6143     /* selects which one we will use, which is than copied to the Board   */
6144     /* initialPosition, which for the rest is initialized by Pawns and    */
6145     /* empty squares. This initial position is then copied to boards[0],  */
6146     /* possibly after shuffling, so that it remains available.            */
6147
6148     gameInfo.holdingsWidth = 0; /* default board sizes */
6149     gameInfo.boardWidth    = 8;
6150     gameInfo.boardHeight   = 8;
6151     gameInfo.holdingsSize  = 0;
6152     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6153     for(i=0; i<BOARD_FILES-6; i++)
6154       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6155     initialPosition[EP_STATUS] = EP_NONE;
6156     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6157     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6158     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6159          SetCharTable(pieceNickName, appData.pieceNickNames);
6160     else SetCharTable(pieceNickName, "............");
6161     pieces = FIDEArray;
6162
6163     switch (gameInfo.variant) {
6164     case VariantFischeRandom:
6165       shuffleOpenings = TRUE;
6166       appData.fischerCastling = TRUE;
6167     default:
6168       break;
6169     case VariantShatranj:
6170       pieces = ShatranjArray;
6171       nrCastlingRights = 0;
6172       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6173       break;
6174     case VariantMakruk:
6175       pieces = makrukArray;
6176       nrCastlingRights = 0;
6177       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6178       break;
6179     case VariantASEAN:
6180       pieces = aseanArray;
6181       nrCastlingRights = 0;
6182       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6183       break;
6184     case VariantTwoKings:
6185       pieces = twoKingsArray;
6186       break;
6187     case VariantGrand:
6188       pieces = GrandArray;
6189       nrCastlingRights = 0;
6190       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6191       gameInfo.boardWidth = 10;
6192       gameInfo.boardHeight = 10;
6193       gameInfo.holdingsSize = 7;
6194       break;
6195     case VariantCapaRandom:
6196       shuffleOpenings = TRUE;
6197       appData.fischerCastling = TRUE;
6198     case VariantCapablanca:
6199       pieces = CapablancaArray;
6200       gameInfo.boardWidth = 10;
6201       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6202       break;
6203     case VariantGothic:
6204       pieces = GothicArray;
6205       gameInfo.boardWidth = 10;
6206       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6207       break;
6208     case VariantSChess:
6209       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6210       gameInfo.holdingsSize = 7;
6211       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6212       break;
6213     case VariantJanus:
6214       pieces = JanusArray;
6215       gameInfo.boardWidth = 10;
6216       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6217       nrCastlingRights = 6;
6218         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6219         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6220         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6221         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6222         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6223         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6224       break;
6225     case VariantFalcon:
6226       pieces = FalconArray;
6227       gameInfo.boardWidth = 10;
6228       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6229       break;
6230     case VariantXiangqi:
6231       pieces = XiangqiArray;
6232       gameInfo.boardWidth  = 9;
6233       gameInfo.boardHeight = 10;
6234       nrCastlingRights = 0;
6235       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6236       break;
6237     case VariantShogi:
6238       pieces = ShogiArray;
6239       gameInfo.boardWidth  = 9;
6240       gameInfo.boardHeight = 9;
6241       gameInfo.holdingsSize = 7;
6242       nrCastlingRights = 0;
6243       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6244       break;
6245     case VariantChu:
6246       pieces = ChuArray; pieceRows = 3;
6247       gameInfo.boardWidth  = 12;
6248       gameInfo.boardHeight = 12;
6249       nrCastlingRights = 0;
6250       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6251                                    "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6252       break;
6253     case VariantCourier:
6254       pieces = CourierArray;
6255       gameInfo.boardWidth  = 12;
6256       nrCastlingRights = 0;
6257       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6258       break;
6259     case VariantKnightmate:
6260       pieces = KnightmateArray;
6261       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6262       break;
6263     case VariantSpartan:
6264       pieces = SpartanArray;
6265       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6266       break;
6267     case VariantLion:
6268       pieces = lionArray;
6269       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6270       break;
6271     case VariantChuChess:
6272       pieces = ChuChessArray;
6273       gameInfo.boardWidth = 10;
6274       gameInfo.boardHeight = 10;
6275       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6276       break;
6277     case VariantFairy:
6278       pieces = fairyArray;
6279       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6280       break;
6281     case VariantGreat:
6282       pieces = GreatArray;
6283       gameInfo.boardWidth = 10;
6284       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6285       gameInfo.holdingsSize = 8;
6286       break;
6287     case VariantSuper:
6288       pieces = FIDEArray;
6289       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6290       gameInfo.holdingsSize = 8;
6291       startedFromSetupPosition = TRUE;
6292       break;
6293     case VariantCrazyhouse:
6294     case VariantBughouse:
6295       pieces = FIDEArray;
6296       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6297       gameInfo.holdingsSize = 5;
6298       break;
6299     case VariantWildCastle:
6300       pieces = FIDEArray;
6301       /* !!?shuffle with kings guaranteed to be on d or e file */
6302       shuffleOpenings = 1;
6303       break;
6304     case VariantNoCastle:
6305       pieces = FIDEArray;
6306       nrCastlingRights = 0;
6307       /* !!?unconstrained back-rank shuffle */
6308       shuffleOpenings = 1;
6309       break;
6310     }
6311
6312     overrule = 0;
6313     if(appData.NrFiles >= 0) {
6314         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6315         gameInfo.boardWidth = appData.NrFiles;
6316     }
6317     if(appData.NrRanks >= 0) {
6318         gameInfo.boardHeight = appData.NrRanks;
6319     }
6320     if(appData.holdingsSize >= 0) {
6321         i = appData.holdingsSize;
6322         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6323         gameInfo.holdingsSize = i;
6324     }
6325     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6326     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6327         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6328
6329     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6330     if(pawnRow < 1) pawnRow = 1;
6331     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6332        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6333     if(gameInfo.variant == VariantChu) pawnRow = 3;
6334
6335     /* User pieceToChar list overrules defaults */
6336     if(appData.pieceToCharTable != NULL)
6337         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6338
6339     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6340
6341         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6342             s = (ChessSquare) 0; /* account holding counts in guard band */
6343         for( i=0; i<BOARD_HEIGHT; i++ )
6344             initialPosition[i][j] = s;
6345
6346         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6347         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6348         initialPosition[pawnRow][j] = WhitePawn;
6349         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6350         if(gameInfo.variant == VariantXiangqi) {
6351             if(j&1) {
6352                 initialPosition[pawnRow][j] =
6353                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6354                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6355                    initialPosition[2][j] = WhiteCannon;
6356                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6357                 }
6358             }
6359         }
6360         if(gameInfo.variant == VariantChu) {
6361              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6362                initialPosition[pawnRow+1][j] = WhiteCobra,
6363                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6364              for(i=1; i<pieceRows; i++) {
6365                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6366                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6367              }
6368         }
6369         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6370             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6371                initialPosition[0][j] = WhiteRook;
6372                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6373             }
6374         }
6375         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6376     }
6377     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6378     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6379
6380             j=BOARD_LEFT+1;
6381             initialPosition[1][j] = WhiteBishop;
6382             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6383             j=BOARD_RGHT-2;
6384             initialPosition[1][j] = WhiteRook;
6385             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6386     }
6387
6388     if( nrCastlingRights == -1) {
6389         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6390         /*       This sets default castling rights from none to normal corners   */
6391         /* Variants with other castling rights must set them themselves above    */
6392         nrCastlingRights = 6;
6393
6394         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6395         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6396         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6397         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6398         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6399         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6400      }
6401
6402      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6403      if(gameInfo.variant == VariantGreat) { // promotion commoners
6404         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6405         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6406         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6407         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6408      }
6409      if( gameInfo.variant == VariantSChess ) {
6410       initialPosition[1][0] = BlackMarshall;
6411       initialPosition[2][0] = BlackAngel;
6412       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6413       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6414       initialPosition[1][1] = initialPosition[2][1] =
6415       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6416      }
6417   if (appData.debugMode) {
6418     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6419   }
6420     if(shuffleOpenings) {
6421         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6422         startedFromSetupPosition = TRUE;
6423     }
6424     if(startedFromPositionFile) {
6425       /* [HGM] loadPos: use PositionFile for every new game */
6426       CopyBoard(initialPosition, filePosition);
6427       for(i=0; i<nrCastlingRights; i++)
6428           initialRights[i] = filePosition[CASTLING][i];
6429       startedFromSetupPosition = TRUE;
6430     }
6431
6432     CopyBoard(boards[0], initialPosition);
6433
6434     if(oldx != gameInfo.boardWidth ||
6435        oldy != gameInfo.boardHeight ||
6436        oldv != gameInfo.variant ||
6437        oldh != gameInfo.holdingsWidth
6438                                          )
6439             InitDrawingSizes(-2 ,0);
6440
6441     oldv = gameInfo.variant;
6442     if (redraw)
6443       DrawPosition(TRUE, boards[currentMove]);
6444 }
6445
6446 void
6447 SendBoard (ChessProgramState *cps, int moveNum)
6448 {
6449     char message[MSG_SIZ];
6450
6451     if (cps->useSetboard) {
6452       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6453       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6454       SendToProgram(message, cps);
6455       free(fen);
6456
6457     } else {
6458       ChessSquare *bp;
6459       int i, j, left=0, right=BOARD_WIDTH;
6460       /* Kludge to set black to move, avoiding the troublesome and now
6461        * deprecated "black" command.
6462        */
6463       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6464         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6465
6466       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6467
6468       SendToProgram("edit\n", cps);
6469       SendToProgram("#\n", cps);
6470       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6471         bp = &boards[moveNum][i][left];
6472         for (j = left; j < right; j++, bp++) {
6473           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6474           if ((int) *bp < (int) BlackPawn) {
6475             if(j == BOARD_RGHT+1)
6476                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6477             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6478             if(message[0] == '+' || message[0] == '~') {
6479               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6480                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6481                         AAA + j, ONE + i - '0');
6482             }
6483             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6484                 message[1] = BOARD_RGHT   - 1 - j + '1';
6485                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6486             }
6487             SendToProgram(message, cps);
6488           }
6489         }
6490       }
6491
6492       SendToProgram("c\n", cps);
6493       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6494         bp = &boards[moveNum][i][left];
6495         for (j = left; j < right; j++, bp++) {
6496           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6497           if (((int) *bp != (int) EmptySquare)
6498               && ((int) *bp >= (int) BlackPawn)) {
6499             if(j == BOARD_LEFT-2)
6500                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6501             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6502                     AAA + j, ONE + i - '0');
6503             if(message[0] == '+' || message[0] == '~') {
6504               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6505                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6506                         AAA + j, ONE + i - '0');
6507             }
6508             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6509                 message[1] = BOARD_RGHT   - 1 - j + '1';
6510                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6511             }
6512             SendToProgram(message, cps);
6513           }
6514         }
6515       }
6516
6517       SendToProgram(".\n", cps);
6518     }
6519     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6520 }
6521
6522 char exclusionHeader[MSG_SIZ];
6523 int exCnt, excludePtr;
6524 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6525 static Exclusion excluTab[200];
6526 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6527
6528 static void
6529 WriteMap (int s)
6530 {
6531     int j;
6532     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6533     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6534 }
6535
6536 static void
6537 ClearMap ()
6538 {
6539     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6540     excludePtr = 24; exCnt = 0;
6541     WriteMap(0);
6542 }
6543
6544 static void
6545 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6546 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6547     char buf[2*MOVE_LEN], *p;
6548     Exclusion *e = excluTab;
6549     int i;
6550     for(i=0; i<exCnt; i++)
6551         if(e[i].ff == fromX && e[i].fr == fromY &&
6552            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6553     if(i == exCnt) { // was not in exclude list; add it
6554         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6555         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6556             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6557             return; // abort
6558         }
6559         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6560         excludePtr++; e[i].mark = excludePtr++;
6561         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6562         exCnt++;
6563     }
6564     exclusionHeader[e[i].mark] = state;
6565 }
6566
6567 static int
6568 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6569 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6570     char buf[MSG_SIZ];
6571     int j, k;
6572     ChessMove moveType;
6573     if((signed char)promoChar == -1) { // kludge to indicate best move
6574         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6575             return 1; // if unparsable, abort
6576     }
6577     // update exclusion map (resolving toggle by consulting existing state)
6578     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6579     j = k%8; k >>= 3;
6580     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6581     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6582          excludeMap[k] |=   1<<j;
6583     else excludeMap[k] &= ~(1<<j);
6584     // update header
6585     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6586     // inform engine
6587     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6588     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6589     SendToBoth(buf);
6590     return (state == '+');
6591 }
6592
6593 static void
6594 ExcludeClick (int index)
6595 {
6596     int i, j;
6597     Exclusion *e = excluTab;
6598     if(index < 25) { // none, best or tail clicked
6599         if(index < 13) { // none: include all
6600             WriteMap(0); // clear map
6601             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6602             SendToBoth("include all\n"); // and inform engine
6603         } else if(index > 18) { // tail
6604             if(exclusionHeader[19] == '-') { // tail was excluded
6605                 SendToBoth("include all\n");
6606                 WriteMap(0); // clear map completely
6607                 // now re-exclude selected moves
6608                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6609                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6610             } else { // tail was included or in mixed state
6611                 SendToBoth("exclude all\n");
6612                 WriteMap(0xFF); // fill map completely
6613                 // now re-include selected moves
6614                 j = 0; // count them
6615                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6616                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6617                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6618             }
6619         } else { // best
6620             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6621         }
6622     } else {
6623         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6624             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6625             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6626             break;
6627         }
6628     }
6629 }
6630
6631 ChessSquare
6632 DefaultPromoChoice (int white)
6633 {
6634     ChessSquare result;
6635     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6636        gameInfo.variant == VariantMakruk)
6637         result = WhiteFerz; // no choice
6638     else if(gameInfo.variant == VariantASEAN)
6639         result = WhiteRook; // no choice
6640     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6641         result= WhiteKing; // in Suicide Q is the last thing we want
6642     else if(gameInfo.variant == VariantSpartan)
6643         result = white ? WhiteQueen : WhiteAngel;
6644     else result = WhiteQueen;
6645     if(!white) result = WHITE_TO_BLACK result;
6646     return result;
6647 }
6648
6649 static int autoQueen; // [HGM] oneclick
6650
6651 int
6652 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6653 {
6654     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6655     /* [HGM] add Shogi promotions */
6656     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6657     ChessSquare piece, partner;
6658     ChessMove moveType;
6659     Boolean premove;
6660
6661     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6662     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6663
6664     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6665       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6666         return FALSE;
6667
6668     piece = boards[currentMove][fromY][fromX];
6669     if(gameInfo.variant == VariantChu) {
6670         promotionZoneSize = BOARD_HEIGHT/3;
6671         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6672     } else if(gameInfo.variant == VariantShogi) {
6673         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6674         highestPromotingPiece = (int)WhiteAlfil;
6675     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6676         promotionZoneSize = 3;
6677     }
6678
6679     // Treat Lance as Pawn when it is not representing Amazon or Lance
6680     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6681         if(piece == WhiteLance) piece = WhitePawn; else
6682         if(piece == BlackLance) piece = BlackPawn;
6683     }
6684
6685     // next weed out all moves that do not touch the promotion zone at all
6686     if((int)piece >= BlackPawn) {
6687         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6688              return FALSE;
6689         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6690         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6691     } else {
6692         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6693            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6694         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6695              return FALSE;
6696     }
6697
6698     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6699
6700     // weed out mandatory Shogi promotions
6701     if(gameInfo.variant == VariantShogi) {
6702         if(piece >= BlackPawn) {
6703             if(toY == 0 && piece == BlackPawn ||
6704                toY == 0 && piece == BlackQueen ||
6705                toY <= 1 && piece == BlackKnight) {
6706                 *promoChoice = '+';
6707                 return FALSE;
6708             }
6709         } else {
6710             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6711                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6712                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6713                 *promoChoice = '+';
6714                 return FALSE;
6715             }
6716         }
6717     }
6718
6719     // weed out obviously illegal Pawn moves
6720     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6721         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6722         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6723         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6724         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6725         // note we are not allowed to test for valid (non-)capture, due to premove
6726     }
6727
6728     // we either have a choice what to promote to, or (in Shogi) whether to promote
6729     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6730        gameInfo.variant == VariantMakruk) {
6731         ChessSquare p=BlackFerz;  // no choice
6732         while(p < EmptySquare) {  //but make sure we use piece that exists
6733             *promoChoice = PieceToChar(p++);
6734             if(*promoChoice != '.') break;
6735         }
6736         return FALSE;
6737     }
6738     // no sense asking what we must promote to if it is going to explode...
6739     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6740         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6741         return FALSE;
6742     }
6743     // give caller the default choice even if we will not make it
6744     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6745     partner = piece; // pieces can promote if the pieceToCharTable says so
6746     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6747     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6748     if(        sweepSelect && gameInfo.variant != VariantGreat
6749                            && gameInfo.variant != VariantGrand
6750                            && gameInfo.variant != VariantSuper) return FALSE;
6751     if(autoQueen) return FALSE; // predetermined
6752
6753     // suppress promotion popup on illegal moves that are not premoves
6754     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6755               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6756     if(appData.testLegality && !premove) {
6757         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6758                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6759         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6760         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6761             return FALSE;
6762     }
6763
6764     return TRUE;
6765 }
6766
6767 int
6768 InPalace (int row, int column)
6769 {   /* [HGM] for Xiangqi */
6770     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6771          column < (BOARD_WIDTH + 4)/2 &&
6772          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6773     return FALSE;
6774 }
6775
6776 int
6777 PieceForSquare (int x, int y)
6778 {
6779   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6780      return -1;
6781   else
6782      return boards[currentMove][y][x];
6783 }
6784
6785 int
6786 OKToStartUserMove (int x, int y)
6787 {
6788     ChessSquare from_piece;
6789     int white_piece;
6790
6791     if (matchMode) return FALSE;
6792     if (gameMode == EditPosition) return TRUE;
6793
6794     if (x >= 0 && y >= 0)
6795       from_piece = boards[currentMove][y][x];
6796     else
6797       from_piece = EmptySquare;
6798
6799     if (from_piece == EmptySquare) return FALSE;
6800
6801     white_piece = (int)from_piece >= (int)WhitePawn &&
6802       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6803
6804     switch (gameMode) {
6805       case AnalyzeFile:
6806       case TwoMachinesPlay:
6807       case EndOfGame:
6808         return FALSE;
6809
6810       case IcsObserving:
6811       case IcsIdle:
6812         return FALSE;
6813
6814       case MachinePlaysWhite:
6815       case IcsPlayingBlack:
6816         if (appData.zippyPlay) return FALSE;
6817         if (white_piece) {
6818             DisplayMoveError(_("You are playing Black"));
6819             return FALSE;
6820         }
6821         break;
6822
6823       case MachinePlaysBlack:
6824       case IcsPlayingWhite:
6825         if (appData.zippyPlay) return FALSE;
6826         if (!white_piece) {
6827             DisplayMoveError(_("You are playing White"));
6828             return FALSE;
6829         }
6830         break;
6831
6832       case PlayFromGameFile:
6833             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6834       case EditGame:
6835         if (!white_piece && WhiteOnMove(currentMove)) {
6836             DisplayMoveError(_("It is White's turn"));
6837             return FALSE;
6838         }
6839         if (white_piece && !WhiteOnMove(currentMove)) {
6840             DisplayMoveError(_("It is Black's turn"));
6841             return FALSE;
6842         }
6843         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6844             /* Editing correspondence game history */
6845             /* Could disallow this or prompt for confirmation */
6846             cmailOldMove = -1;
6847         }
6848         break;
6849
6850       case BeginningOfGame:
6851         if (appData.icsActive) return FALSE;
6852         if (!appData.noChessProgram) {
6853             if (!white_piece) {
6854                 DisplayMoveError(_("You are playing White"));
6855                 return FALSE;
6856             }
6857         }
6858         break;
6859
6860       case Training:
6861         if (!white_piece && WhiteOnMove(currentMove)) {
6862             DisplayMoveError(_("It is White's turn"));
6863             return FALSE;
6864         }
6865         if (white_piece && !WhiteOnMove(currentMove)) {
6866             DisplayMoveError(_("It is Black's turn"));
6867             return FALSE;
6868         }
6869         break;
6870
6871       default:
6872       case IcsExamining:
6873         break;
6874     }
6875     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6876         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6877         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6878         && gameMode != AnalyzeFile && gameMode != Training) {
6879         DisplayMoveError(_("Displayed position is not current"));
6880         return FALSE;
6881     }
6882     return TRUE;
6883 }
6884
6885 Boolean
6886 OnlyMove (int *x, int *y, Boolean captures)
6887 {
6888     DisambiguateClosure cl;
6889     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6890     switch(gameMode) {
6891       case MachinePlaysBlack:
6892       case IcsPlayingWhite:
6893       case BeginningOfGame:
6894         if(!WhiteOnMove(currentMove)) return FALSE;
6895         break;
6896       case MachinePlaysWhite:
6897       case IcsPlayingBlack:
6898         if(WhiteOnMove(currentMove)) return FALSE;
6899         break;
6900       case EditGame:
6901         break;
6902       default:
6903         return FALSE;
6904     }
6905     cl.pieceIn = EmptySquare;
6906     cl.rfIn = *y;
6907     cl.ffIn = *x;
6908     cl.rtIn = -1;
6909     cl.ftIn = -1;
6910     cl.promoCharIn = NULLCHAR;
6911     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6912     if( cl.kind == NormalMove ||
6913         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6914         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6915         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6916       fromX = cl.ff;
6917       fromY = cl.rf;
6918       *x = cl.ft;
6919       *y = cl.rt;
6920       return TRUE;
6921     }
6922     if(cl.kind != ImpossibleMove) return FALSE;
6923     cl.pieceIn = EmptySquare;
6924     cl.rfIn = -1;
6925     cl.ffIn = -1;
6926     cl.rtIn = *y;
6927     cl.ftIn = *x;
6928     cl.promoCharIn = NULLCHAR;
6929     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6930     if( cl.kind == NormalMove ||
6931         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6932         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6933         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6934       fromX = cl.ff;
6935       fromY = cl.rf;
6936       *x = cl.ft;
6937       *y = cl.rt;
6938       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6939       return TRUE;
6940     }
6941     return FALSE;
6942 }
6943
6944 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6945 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6946 int lastLoadGameUseList = FALSE;
6947 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6948 ChessMove lastLoadGameStart = EndOfFile;
6949 int doubleClick;
6950 Boolean addToBookFlag;
6951
6952 void
6953 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6954 {
6955     ChessMove moveType;
6956     ChessSquare pup;
6957     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6958
6959     /* Check if the user is playing in turn.  This is complicated because we
6960        let the user "pick up" a piece before it is his turn.  So the piece he
6961        tried to pick up may have been captured by the time he puts it down!
6962        Therefore we use the color the user is supposed to be playing in this
6963        test, not the color of the piece that is currently on the starting
6964        square---except in EditGame mode, where the user is playing both
6965        sides; fortunately there the capture race can't happen.  (It can
6966        now happen in IcsExamining mode, but that's just too bad.  The user
6967        will get a somewhat confusing message in that case.)
6968        */
6969
6970     switch (gameMode) {
6971       case AnalyzeFile:
6972       case TwoMachinesPlay:
6973       case EndOfGame:
6974       case IcsObserving:
6975       case IcsIdle:
6976         /* We switched into a game mode where moves are not accepted,
6977            perhaps while the mouse button was down. */
6978         return;
6979
6980       case MachinePlaysWhite:
6981         /* User is moving for Black */
6982         if (WhiteOnMove(currentMove)) {
6983             DisplayMoveError(_("It is White's turn"));
6984             return;
6985         }
6986         break;
6987
6988       case MachinePlaysBlack:
6989         /* User is moving for White */
6990         if (!WhiteOnMove(currentMove)) {
6991             DisplayMoveError(_("It is Black's turn"));
6992             return;
6993         }
6994         break;
6995
6996       case PlayFromGameFile:
6997             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6998       case EditGame:
6999       case IcsExamining:
7000       case BeginningOfGame:
7001       case AnalyzeMode:
7002       case Training:
7003         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7004         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7005             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7006             /* User is moving for Black */
7007             if (WhiteOnMove(currentMove)) {
7008                 DisplayMoveError(_("It is White's turn"));
7009                 return;
7010             }
7011         } else {
7012             /* User is moving for White */
7013             if (!WhiteOnMove(currentMove)) {
7014                 DisplayMoveError(_("It is Black's turn"));
7015                 return;
7016             }
7017         }
7018         break;
7019
7020       case IcsPlayingBlack:
7021         /* User is moving for Black */
7022         if (WhiteOnMove(currentMove)) {
7023             if (!appData.premove) {
7024                 DisplayMoveError(_("It is White's turn"));
7025             } else if (toX >= 0 && toY >= 0) {
7026                 premoveToX = toX;
7027                 premoveToY = toY;
7028                 premoveFromX = fromX;
7029                 premoveFromY = fromY;
7030                 premovePromoChar = promoChar;
7031                 gotPremove = 1;
7032                 if (appData.debugMode)
7033                     fprintf(debugFP, "Got premove: fromX %d,"
7034                             "fromY %d, toX %d, toY %d\n",
7035                             fromX, fromY, toX, toY);
7036             }
7037             return;
7038         }
7039         break;
7040
7041       case IcsPlayingWhite:
7042         /* User is moving for White */
7043         if (!WhiteOnMove(currentMove)) {
7044             if (!appData.premove) {
7045                 DisplayMoveError(_("It is Black's turn"));
7046             } else if (toX >= 0 && toY >= 0) {
7047                 premoveToX = toX;
7048                 premoveToY = toY;
7049                 premoveFromX = fromX;
7050                 premoveFromY = fromY;
7051                 premovePromoChar = promoChar;
7052                 gotPremove = 1;
7053                 if (appData.debugMode)
7054                     fprintf(debugFP, "Got premove: fromX %d,"
7055                             "fromY %d, toX %d, toY %d\n",
7056                             fromX, fromY, toX, toY);
7057             }
7058             return;
7059         }
7060         break;
7061
7062       default:
7063         break;
7064
7065       case EditPosition:
7066         /* EditPosition, empty square, or different color piece;
7067            click-click move is possible */
7068         if (toX == -2 || toY == -2) {
7069             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7070             DrawPosition(FALSE, boards[currentMove]);
7071             return;
7072         } else if (toX >= 0 && toY >= 0) {
7073             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7074                 ChessSquare p = boards[0][rf][ff];
7075                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7076                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); 
7077             }
7078             boards[0][toY][toX] = boards[0][fromY][fromX];
7079             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7080                 if(boards[0][fromY][0] != EmptySquare) {
7081                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7082                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7083                 }
7084             } else
7085             if(fromX == BOARD_RGHT+1) {
7086                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7087                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7088                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7089                 }
7090             } else
7091             boards[0][fromY][fromX] = gatingPiece;
7092             ClearHighlights();
7093             DrawPosition(FALSE, boards[currentMove]);
7094             return;
7095         }
7096         return;
7097     }
7098
7099     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7100     pup = boards[currentMove][toY][toX];
7101
7102     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7103     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7104          if( pup != EmptySquare ) return;
7105          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7106            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7107                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7108            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7109            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7110            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7111            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7112          fromY = DROP_RANK;
7113     }
7114
7115     /* [HGM] always test for legality, to get promotion info */
7116     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7117                                          fromY, fromX, toY, toX, promoChar);
7118
7119     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7120
7121     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7122
7123     /* [HGM] but possibly ignore an IllegalMove result */
7124     if (appData.testLegality) {
7125         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7126             DisplayMoveError(_("Illegal move"));
7127             return;
7128         }
7129     }
7130
7131     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7132         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7133              ClearPremoveHighlights(); // was included
7134         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7135         return;
7136     }
7137
7138     if(addToBookFlag) { // adding moves to book
7139         char buf[MSG_SIZ], move[MSG_SIZ];
7140         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7141         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d", fromX + AAA, fromY + ONE - '0', killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0');
7142         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7143         AddBookMove(buf);
7144         addToBookFlag = FALSE;
7145         ClearHighlights();
7146         return;
7147     }
7148
7149     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7150 }
7151
7152 /* Common tail of UserMoveEvent and DropMenuEvent */
7153 int
7154 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7155 {
7156     char *bookHit = 0;
7157
7158     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7159         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7160         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7161         if(WhiteOnMove(currentMove)) {
7162             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7163         } else {
7164             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7165         }
7166     }
7167
7168     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7169        move type in caller when we know the move is a legal promotion */
7170     if(moveType == NormalMove && promoChar)
7171         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7172
7173     /* [HGM] <popupFix> The following if has been moved here from
7174        UserMoveEvent(). Because it seemed to belong here (why not allow
7175        piece drops in training games?), and because it can only be
7176        performed after it is known to what we promote. */
7177     if (gameMode == Training) {
7178       /* compare the move played on the board to the next move in the
7179        * game. If they match, display the move and the opponent's response.
7180        * If they don't match, display an error message.
7181        */
7182       int saveAnimate;
7183       Board testBoard;
7184       CopyBoard(testBoard, boards[currentMove]);
7185       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7186
7187       if (CompareBoards(testBoard, boards[currentMove+1])) {
7188         ForwardInner(currentMove+1);
7189
7190         /* Autoplay the opponent's response.
7191          * if appData.animate was TRUE when Training mode was entered,
7192          * the response will be animated.
7193          */
7194         saveAnimate = appData.animate;
7195         appData.animate = animateTraining;
7196         ForwardInner(currentMove+1);
7197         appData.animate = saveAnimate;
7198
7199         /* check for the end of the game */
7200         if (currentMove >= forwardMostMove) {
7201           gameMode = PlayFromGameFile;
7202           ModeHighlight();
7203           SetTrainingModeOff();
7204           DisplayInformation(_("End of game"));
7205         }
7206       } else {
7207         DisplayError(_("Incorrect move"), 0);
7208       }
7209       return 1;
7210     }
7211
7212   /* Ok, now we know that the move is good, so we can kill
7213      the previous line in Analysis Mode */
7214   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7215                                 && currentMove < forwardMostMove) {
7216     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7217     else forwardMostMove = currentMove;
7218   }
7219
7220   ClearMap();
7221
7222   /* If we need the chess program but it's dead, restart it */
7223   ResurrectChessProgram();
7224
7225   /* A user move restarts a paused game*/
7226   if (pausing)
7227     PauseEvent();
7228
7229   thinkOutput[0] = NULLCHAR;
7230
7231   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7232
7233   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7234     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7235     return 1;
7236   }
7237
7238   if (gameMode == BeginningOfGame) {
7239     if (appData.noChessProgram) {
7240       gameMode = EditGame;
7241       SetGameInfo();
7242     } else {
7243       char buf[MSG_SIZ];
7244       gameMode = MachinePlaysBlack;
7245       StartClocks();
7246       SetGameInfo();
7247       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7248       DisplayTitle(buf);
7249       if (first.sendName) {
7250         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7251         SendToProgram(buf, &first);
7252       }
7253       StartClocks();
7254     }
7255     ModeHighlight();
7256   }
7257
7258   /* Relay move to ICS or chess engine */
7259   if (appData.icsActive) {
7260     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7261         gameMode == IcsExamining) {
7262       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7263         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7264         SendToICS("draw ");
7265         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7266       }
7267       // also send plain move, in case ICS does not understand atomic claims
7268       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7269       ics_user_moved = 1;
7270     }
7271   } else {
7272     if (first.sendTime && (gameMode == BeginningOfGame ||
7273                            gameMode == MachinePlaysWhite ||
7274                            gameMode == MachinePlaysBlack)) {
7275       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7276     }
7277     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7278          // [HGM] book: if program might be playing, let it use book
7279         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7280         first.maybeThinking = TRUE;
7281     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7282         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7283         SendBoard(&first, currentMove+1);
7284         if(second.analyzing) {
7285             if(!second.useSetboard) SendToProgram("undo\n", &second);
7286             SendBoard(&second, currentMove+1);
7287         }
7288     } else {
7289         SendMoveToProgram(forwardMostMove-1, &first);
7290         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7291     }
7292     if (currentMove == cmailOldMove + 1) {
7293       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7294     }
7295   }
7296
7297   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7298
7299   switch (gameMode) {
7300   case EditGame:
7301     if(appData.testLegality)
7302     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7303     case MT_NONE:
7304     case MT_CHECK:
7305       break;
7306     case MT_CHECKMATE:
7307     case MT_STAINMATE:
7308       if (WhiteOnMove(currentMove)) {
7309         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7310       } else {
7311         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7312       }
7313       break;
7314     case MT_STALEMATE:
7315       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7316       break;
7317     }
7318     break;
7319
7320   case MachinePlaysBlack:
7321   case MachinePlaysWhite:
7322     /* disable certain menu options while machine is thinking */
7323     SetMachineThinkingEnables();
7324     break;
7325
7326   default:
7327     break;
7328   }
7329
7330   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7331   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7332
7333   if(bookHit) { // [HGM] book: simulate book reply
7334         static char bookMove[MSG_SIZ]; // a bit generous?
7335
7336         programStats.nodes = programStats.depth = programStats.time =
7337         programStats.score = programStats.got_only_move = 0;
7338         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7339
7340         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7341         strcat(bookMove, bookHit);
7342         HandleMachineMove(bookMove, &first);
7343   }
7344   return 1;
7345 }
7346
7347 void
7348 MarkByFEN(char *fen)
7349 {
7350         int r, f;
7351         if(!appData.markers || !appData.highlightDragging) return;
7352         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7353         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7354         while(*fen) {
7355             int s = 0;
7356             marker[r][f] = 0;
7357             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7358             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7359             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7360             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7361             if(*fen == 'T') marker[r][f++] = 0; else
7362             if(*fen == 'Y') marker[r][f++] = 1; else
7363             if(*fen == 'G') marker[r][f++] = 3; else
7364             if(*fen == 'B') marker[r][f++] = 4; else
7365             if(*fen == 'C') marker[r][f++] = 5; else
7366             if(*fen == 'M') marker[r][f++] = 6; else
7367             if(*fen == 'W') marker[r][f++] = 7; else
7368             if(*fen == 'D') marker[r][f++] = 8; else
7369             if(*fen == 'R') marker[r][f++] = 2; else {
7370                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7371               f += s; fen -= s>0;
7372             }
7373             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7374             if(r < 0) break;
7375             fen++;
7376         }
7377         DrawPosition(TRUE, NULL);
7378 }
7379
7380 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7381
7382 void
7383 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7384 {
7385     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7386     Markers *m = (Markers *) closure;
7387     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7388         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7389                          || kind == WhiteCapturesEnPassant
7390                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 3;
7391     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7392 }
7393
7394 static int hoverSavedValid;
7395
7396 void
7397 MarkTargetSquares (int clear)
7398 {
7399   int x, y, sum=0;
7400   if(clear) { // no reason to ever suppress clearing
7401     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7402     hoverSavedValid = 0;
7403     if(!sum) return; // nothing was cleared,no redraw needed
7404   } else {
7405     int capt = 0;
7406     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7407        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7408     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7409     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7410       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7411       if(capt)
7412       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7413     }
7414   }
7415   DrawPosition(FALSE, NULL);
7416 }
7417
7418 int
7419 Explode (Board board, int fromX, int fromY, int toX, int toY)
7420 {
7421     if(gameInfo.variant == VariantAtomic &&
7422        (board[toY][toX] != EmptySquare ||                     // capture?
7423         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7424                          board[fromY][fromX] == BlackPawn   )
7425       )) {
7426         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7427         return TRUE;
7428     }
7429     return FALSE;
7430 }
7431
7432 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7433
7434 int
7435 CanPromote (ChessSquare piece, int y)
7436 {
7437         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7438         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7439         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7440         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7441            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7442            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7443          gameInfo.variant == VariantMakruk) return FALSE;
7444         return (piece == BlackPawn && y <= zone ||
7445                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7446                 piece == BlackLance && y <= zone ||
7447                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7448 }
7449
7450 void
7451 HoverEvent (int xPix, int yPix, int x, int y)
7452 {
7453         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7454         int r, f;
7455         if(!first.highlight) return;
7456         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7457         if(x == oldX && y == oldY) return; // only do something if we enter new square
7458         oldFromX = fromX; oldFromY = fromY;
7459         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7460           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7461             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7462           hoverSavedValid = 1;
7463         } else if(oldX != x || oldY != y) {
7464           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7465           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7466           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7467             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7468           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7469             char buf[MSG_SIZ];
7470             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7471             SendToProgram(buf, &first);
7472           }
7473           oldX = x; oldY = y;
7474 //        SetHighlights(fromX, fromY, x, y);
7475         }
7476 }
7477
7478 void ReportClick(char *action, int x, int y)
7479 {
7480         char buf[MSG_SIZ]; // Inform engine of what user does
7481         int r, f;
7482         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7483           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7484             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7485         if(!first.highlight || gameMode == EditPosition) return;
7486         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7487         SendToProgram(buf, &first);
7488 }
7489
7490 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7491
7492 void
7493 LeftClick (ClickType clickType, int xPix, int yPix)
7494 {
7495     int x, y;
7496     Boolean saveAnimate;
7497     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7498     char promoChoice = NULLCHAR;
7499     ChessSquare piece;
7500     static TimeMark lastClickTime, prevClickTime;
7501
7502     x = EventToSquare(xPix, BOARD_WIDTH);
7503     y = EventToSquare(yPix, BOARD_HEIGHT);
7504     if (!flipView && y >= 0) {
7505         y = BOARD_HEIGHT - 1 - y;
7506     }
7507     if (flipView && x >= 0) {
7508         x = BOARD_WIDTH - 1 - x;
7509     }
7510
7511     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7512         static int dummy;
7513         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7514         right = TRUE;
7515         return;
7516     }
7517
7518     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7519
7520     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7521
7522     if (clickType == Press) ErrorPopDown();
7523     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7524
7525     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7526         defaultPromoChoice = promoSweep;
7527         promoSweep = EmptySquare;   // terminate sweep
7528         promoDefaultAltered = TRUE;
7529         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7530     }
7531
7532     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7533         if(clickType == Release) return; // ignore upclick of click-click destination
7534         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7535         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7536         if(gameInfo.holdingsWidth &&
7537                 (WhiteOnMove(currentMove)
7538                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7539                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7540             // click in right holdings, for determining promotion piece
7541             ChessSquare p = boards[currentMove][y][x];
7542             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7543             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7544             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7545                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7546                 fromX = fromY = -1;
7547                 return;
7548             }
7549         }
7550         DrawPosition(FALSE, boards[currentMove]);
7551         return;
7552     }
7553
7554     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7555     if(clickType == Press
7556             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7557               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7558               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7559         return;
7560
7561     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7562         // could be static click on premove from-square: abort premove
7563         gotPremove = 0;
7564         ClearPremoveHighlights();
7565     }
7566
7567     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7568         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7569
7570     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7571         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7572                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7573         defaultPromoChoice = DefaultPromoChoice(side);
7574     }
7575
7576     autoQueen = appData.alwaysPromoteToQueen;
7577
7578     if (fromX == -1) {
7579       int originalY = y;
7580       gatingPiece = EmptySquare;
7581       if (clickType != Press) {
7582         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7583             DragPieceEnd(xPix, yPix); dragging = 0;
7584             DrawPosition(FALSE, NULL);
7585         }
7586         return;
7587       }
7588       doubleClick = FALSE;
7589       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7590         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7591       }
7592       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7593       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7594          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7595          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7596             /* First square */
7597             if (OKToStartUserMove(fromX, fromY)) {
7598                 second = 0;
7599                 ReportClick("lift", x, y);
7600                 MarkTargetSquares(0);
7601                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7602                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7603                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7604                     promoSweep = defaultPromoChoice;
7605                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7606                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7607                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7608                 }
7609                 if (appData.highlightDragging) {
7610                     SetHighlights(fromX, fromY, -1, -1);
7611                 } else {
7612                     ClearHighlights();
7613                 }
7614             } else fromX = fromY = -1;
7615             return;
7616         }
7617     }
7618
7619     /* fromX != -1 */
7620     if (clickType == Press && gameMode != EditPosition) {
7621         ChessSquare fromP;
7622         ChessSquare toP;
7623         int frc;
7624
7625         // ignore off-board to clicks
7626         if(y < 0 || x < 0) return;
7627
7628         /* Check if clicking again on the same color piece */
7629         fromP = boards[currentMove][fromY][fromX];
7630         toP = boards[currentMove][y][x];
7631         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7632         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7633             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7634            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7635              WhitePawn <= toP && toP <= WhiteKing &&
7636              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7637              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7638             (BlackPawn <= fromP && fromP <= BlackKing &&
7639              BlackPawn <= toP && toP <= BlackKing &&
7640              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7641              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7642             /* Clicked again on same color piece -- changed his mind */
7643             second = (x == fromX && y == fromY);
7644             killX = killY = -1;
7645             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7646                 second = FALSE; // first double-click rather than scond click
7647                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7648             }
7649             promoDefaultAltered = FALSE;
7650             MarkTargetSquares(1);
7651            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7652             if (appData.highlightDragging) {
7653                 SetHighlights(x, y, -1, -1);
7654             } else {
7655                 ClearHighlights();
7656             }
7657             if (OKToStartUserMove(x, y)) {
7658                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7659                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7660                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7661                  gatingPiece = boards[currentMove][fromY][fromX];
7662                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7663                 fromX = x;
7664                 fromY = y; dragging = 1;
7665                 if(!second) ReportClick("lift", x, y);
7666                 MarkTargetSquares(0);
7667                 DragPieceBegin(xPix, yPix, FALSE);
7668                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7669                     promoSweep = defaultPromoChoice;
7670                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7671                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7672                 }
7673             }
7674            }
7675            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7676            second = FALSE;
7677         }
7678         // ignore clicks on holdings
7679         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7680     }
7681
7682     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7683         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7684         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7685         return;
7686     }
7687
7688     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7689         DragPieceEnd(xPix, yPix); dragging = 0;
7690         if(clearFlag) {
7691             // a deferred attempt to click-click move an empty square on top of a piece
7692             boards[currentMove][y][x] = EmptySquare;
7693             ClearHighlights();
7694             DrawPosition(FALSE, boards[currentMove]);
7695             fromX = fromY = -1; clearFlag = 0;
7696             return;
7697         }
7698         if (appData.animateDragging) {
7699             /* Undo animation damage if any */
7700             DrawPosition(FALSE, NULL);
7701         }
7702         if (second) {
7703             /* Second up/down in same square; just abort move */
7704             second = 0;
7705             fromX = fromY = -1;
7706             gatingPiece = EmptySquare;
7707             MarkTargetSquares(1);
7708             ClearHighlights();
7709             gotPremove = 0;
7710             ClearPremoveHighlights();
7711         } else {
7712             /* First upclick in same square; start click-click mode */
7713             SetHighlights(x, y, -1, -1);
7714         }
7715         return;
7716     }
7717
7718     clearFlag = 0;
7719
7720     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7721        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7722         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7723         DisplayMessage(_("only marked squares are legal"),"");
7724         DrawPosition(TRUE, NULL);
7725         return; // ignore to-click
7726     }
7727
7728     /* we now have a different from- and (possibly off-board) to-square */
7729     /* Completed move */
7730     if(!sweepSelecting) {
7731         toX = x;
7732         toY = y;
7733     }
7734
7735     piece = boards[currentMove][fromY][fromX];
7736
7737     saveAnimate = appData.animate;
7738     if (clickType == Press) {
7739         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7740         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7741             // must be Edit Position mode with empty-square selected
7742             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7743             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7744             return;
7745         }
7746         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7747             return;
7748         }
7749         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7750             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7751         } else
7752         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7753         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7754           if(appData.sweepSelect) {
7755             promoSweep = defaultPromoChoice;
7756             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7757             selectFlag = 0; lastX = xPix; lastY = yPix;
7758             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7759             Sweep(0); // Pawn that is going to promote: preview promotion piece
7760             sweepSelecting = 1;
7761             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7762             MarkTargetSquares(1);
7763           }
7764           return; // promo popup appears on up-click
7765         }
7766         /* Finish clickclick move */
7767         if (appData.animate || appData.highlightLastMove) {
7768             SetHighlights(fromX, fromY, toX, toY);
7769         } else {
7770             ClearHighlights();
7771         }
7772     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7773         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7774         *promoRestrict = 0;
7775         if (appData.animate || appData.highlightLastMove) {
7776             SetHighlights(fromX, fromY, toX, toY);
7777         } else {
7778             ClearHighlights();
7779         }
7780     } else {
7781 #if 0
7782 // [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
7783         /* Finish drag move */
7784         if (appData.highlightLastMove) {
7785             SetHighlights(fromX, fromY, toX, toY);
7786         } else {
7787             ClearHighlights();
7788         }
7789 #endif
7790         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7791           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7792         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7793         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7794           dragging *= 2;            // flag button-less dragging if we are dragging
7795           MarkTargetSquares(1);
7796           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7797           else {
7798             kill2X = killX; kill2Y = killY;
7799             killX = x; killY = y;     //remeber this square as intermediate
7800             ReportClick("put", x, y); // and inform engine
7801             ReportClick("lift", x, y);
7802             MarkTargetSquares(0);
7803             return;
7804           }
7805         }
7806         DragPieceEnd(xPix, yPix); dragging = 0;
7807         /* Don't animate move and drag both */
7808         appData.animate = FALSE;
7809     }
7810
7811     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7812     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7813         ChessSquare piece = boards[currentMove][fromY][fromX];
7814         if(gameMode == EditPosition && piece != EmptySquare &&
7815            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7816             int n;
7817
7818             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7819                 n = PieceToNumber(piece - (int)BlackPawn);
7820                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7821                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7822                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7823             } else
7824             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7825                 n = PieceToNumber(piece);
7826                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7827                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7828                 boards[currentMove][n][BOARD_WIDTH-2]++;
7829             }
7830             boards[currentMove][fromY][fromX] = EmptySquare;
7831         }
7832         ClearHighlights();
7833         fromX = fromY = -1;
7834         MarkTargetSquares(1);
7835         DrawPosition(TRUE, boards[currentMove]);
7836         return;
7837     }
7838
7839     // off-board moves should not be highlighted
7840     if(x < 0 || y < 0) ClearHighlights();
7841     else ReportClick("put", x, y);
7842
7843     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7844
7845     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7846
7847     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7848         SetHighlights(fromX, fromY, toX, toY);
7849         MarkTargetSquares(1);
7850         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7851             // [HGM] super: promotion to captured piece selected from holdings
7852             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7853             promotionChoice = TRUE;
7854             // kludge follows to temporarily execute move on display, without promoting yet
7855             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7856             boards[currentMove][toY][toX] = p;
7857             DrawPosition(FALSE, boards[currentMove]);
7858             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7859             boards[currentMove][toY][toX] = q;
7860             DisplayMessage("Click in holdings to choose piece", "");
7861             return;
7862         }
7863         PromotionPopUp(promoChoice);
7864     } else {
7865         int oldMove = currentMove;
7866         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7867         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7868         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7869         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7870            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7871             DrawPosition(TRUE, boards[currentMove]);
7872         MarkTargetSquares(1);
7873         fromX = fromY = -1;
7874     }
7875     appData.animate = saveAnimate;
7876     if (appData.animate || appData.animateDragging) {
7877         /* Undo animation damage if needed */
7878         DrawPosition(FALSE, NULL);
7879     }
7880 }
7881
7882 int
7883 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7884 {   // front-end-free part taken out of PieceMenuPopup
7885     int whichMenu; int xSqr, ySqr;
7886
7887     if(seekGraphUp) { // [HGM] seekgraph
7888         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7889         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7890         return -2;
7891     }
7892
7893     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7894          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7895         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7896         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7897         if(action == Press)   {
7898             originalFlip = flipView;
7899             flipView = !flipView; // temporarily flip board to see game from partners perspective
7900             DrawPosition(TRUE, partnerBoard);
7901             DisplayMessage(partnerStatus, "");
7902             partnerUp = TRUE;
7903         } else if(action == Release) {
7904             flipView = originalFlip;
7905             DrawPosition(TRUE, boards[currentMove]);
7906             partnerUp = FALSE;
7907         }
7908         return -2;
7909     }
7910
7911     xSqr = EventToSquare(x, BOARD_WIDTH);
7912     ySqr = EventToSquare(y, BOARD_HEIGHT);
7913     if (action == Release) {
7914         if(pieceSweep != EmptySquare) {
7915             EditPositionMenuEvent(pieceSweep, toX, toY);
7916             pieceSweep = EmptySquare;
7917         } else UnLoadPV(); // [HGM] pv
7918     }
7919     if (action != Press) return -2; // return code to be ignored
7920     switch (gameMode) {
7921       case IcsExamining:
7922         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7923       case EditPosition:
7924         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7925         if (xSqr < 0 || ySqr < 0) return -1;
7926         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7927         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7928         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7929         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7930         NextPiece(0);
7931         return 2; // grab
7932       case IcsObserving:
7933         if(!appData.icsEngineAnalyze) return -1;
7934       case IcsPlayingWhite:
7935       case IcsPlayingBlack:
7936         if(!appData.zippyPlay) goto noZip;
7937       case AnalyzeMode:
7938       case AnalyzeFile:
7939       case MachinePlaysWhite:
7940       case MachinePlaysBlack:
7941       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7942         if (!appData.dropMenu) {
7943           LoadPV(x, y);
7944           return 2; // flag front-end to grab mouse events
7945         }
7946         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7947            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7948       case EditGame:
7949       noZip:
7950         if (xSqr < 0 || ySqr < 0) return -1;
7951         if (!appData.dropMenu || appData.testLegality &&
7952             gameInfo.variant != VariantBughouse &&
7953             gameInfo.variant != VariantCrazyhouse) return -1;
7954         whichMenu = 1; // drop menu
7955         break;
7956       default:
7957         return -1;
7958     }
7959
7960     if (((*fromX = xSqr) < 0) ||
7961         ((*fromY = ySqr) < 0)) {
7962         *fromX = *fromY = -1;
7963         return -1;
7964     }
7965     if (flipView)
7966       *fromX = BOARD_WIDTH - 1 - *fromX;
7967     else
7968       *fromY = BOARD_HEIGHT - 1 - *fromY;
7969
7970     return whichMenu;
7971 }
7972
7973 void
7974 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7975 {
7976 //    char * hint = lastHint;
7977     FrontEndProgramStats stats;
7978
7979     stats.which = cps == &first ? 0 : 1;
7980     stats.depth = cpstats->depth;
7981     stats.nodes = cpstats->nodes;
7982     stats.score = cpstats->score;
7983     stats.time = cpstats->time;
7984     stats.pv = cpstats->movelist;
7985     stats.hint = lastHint;
7986     stats.an_move_index = 0;
7987     stats.an_move_count = 0;
7988
7989     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7990         stats.hint = cpstats->move_name;
7991         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7992         stats.an_move_count = cpstats->nr_moves;
7993     }
7994
7995     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
7996
7997     SetProgramStats( &stats );
7998 }
7999
8000 void
8001 ClearEngineOutputPane (int which)
8002 {
8003     static FrontEndProgramStats dummyStats;
8004     dummyStats.which = which;
8005     dummyStats.pv = "#";
8006     SetProgramStats( &dummyStats );
8007 }
8008
8009 #define MAXPLAYERS 500
8010
8011 char *
8012 TourneyStandings (int display)
8013 {
8014     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8015     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8016     char result, *p, *names[MAXPLAYERS];
8017
8018     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8019         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8020     names[0] = p = strdup(appData.participants);
8021     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8022
8023     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8024
8025     while(result = appData.results[nr]) {
8026         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8027         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8028         wScore = bScore = 0;
8029         switch(result) {
8030           case '+': wScore = 2; break;
8031           case '-': bScore = 2; break;
8032           case '=': wScore = bScore = 1; break;
8033           case ' ':
8034           case '*': return strdup("busy"); // tourney not finished
8035         }
8036         score[w] += wScore;
8037         score[b] += bScore;
8038         games[w]++;
8039         games[b]++;
8040         nr++;
8041     }
8042     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8043     for(w=0; w<nPlayers; w++) {
8044         bScore = -1;
8045         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8046         ranking[w] = b; points[w] = bScore; score[b] = -2;
8047     }
8048     p = malloc(nPlayers*34+1);
8049     for(w=0; w<nPlayers && w<display; w++)
8050         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8051     free(names[0]);
8052     return p;
8053 }
8054
8055 void
8056 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8057 {       // count all piece types
8058         int p, f, r;
8059         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8060         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8061         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8062                 p = board[r][f];
8063                 pCnt[p]++;
8064                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8065                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8066                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8067                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8068                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8069                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8070         }
8071 }
8072
8073 int
8074 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8075 {
8076         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8077         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8078
8079         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8080         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8081         if(myPawns == 2 && nMine == 3) // KPP
8082             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8083         if(myPawns == 1 && nMine == 2) // KP
8084             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8085         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8086             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8087         if(myPawns) return FALSE;
8088         if(pCnt[WhiteRook+side])
8089             return pCnt[BlackRook-side] ||
8090                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8091                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8092                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8093         if(pCnt[WhiteCannon+side]) {
8094             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8095             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8096         }
8097         if(pCnt[WhiteKnight+side])
8098             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8099         return FALSE;
8100 }
8101
8102 int
8103 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8104 {
8105         VariantClass v = gameInfo.variant;
8106
8107         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8108         if(v == VariantShatranj) return TRUE; // always winnable through baring
8109         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8110         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8111
8112         if(v == VariantXiangqi) {
8113                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8114
8115                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8116                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8117                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8118                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8119                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8120                 if(stale) // we have at least one last-rank P plus perhaps C
8121                     return majors // KPKX
8122                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8123                 else // KCA*E*
8124                     return pCnt[WhiteFerz+side] // KCAK
8125                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8126                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8127                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8128
8129         } else if(v == VariantKnightmate) {
8130                 if(nMine == 1) return FALSE;
8131                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8132         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8133                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8134
8135                 if(nMine == 1) return FALSE; // bare King
8136                 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
8137                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8138                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8139                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8140                 if(pCnt[WhiteKnight+side])
8141                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8142                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8143                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8144                 if(nBishops)
8145                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8146                 if(pCnt[WhiteAlfil+side])
8147                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8148                 if(pCnt[WhiteWazir+side])
8149                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8150         }
8151
8152         return TRUE;
8153 }
8154
8155 int
8156 CompareWithRights (Board b1, Board b2)
8157 {
8158     int rights = 0;
8159     if(!CompareBoards(b1, b2)) return FALSE;
8160     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8161     /* compare castling rights */
8162     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8163            rights++; /* King lost rights, while rook still had them */
8164     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8165         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8166            rights++; /* but at least one rook lost them */
8167     }
8168     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8169            rights++;
8170     if( b1[CASTLING][5] != NoRights ) {
8171         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8172            rights++;
8173     }
8174     return rights == 0;
8175 }
8176
8177 int
8178 Adjudicate (ChessProgramState *cps)
8179 {       // [HGM] some adjudications useful with buggy engines
8180         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8181         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8182         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8183         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8184         int k, drop, count = 0; static int bare = 1;
8185         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8186         Boolean canAdjudicate = !appData.icsActive;
8187
8188         // most tests only when we understand the game, i.e. legality-checking on
8189             if( appData.testLegality )
8190             {   /* [HGM] Some more adjudications for obstinate engines */
8191                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8192                 static int moveCount = 6;
8193                 ChessMove result;
8194                 char *reason = NULL;
8195
8196                 /* Count what is on board. */
8197                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8198
8199                 /* Some material-based adjudications that have to be made before stalemate test */
8200                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8201                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8202                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8203                      if(canAdjudicate && appData.checkMates) {
8204                          if(engineOpponent)
8205                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8206                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8207                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8208                          return 1;
8209                      }
8210                 }
8211
8212                 /* Bare King in Shatranj (loses) or Losers (wins) */
8213                 if( nrW == 1 || nrB == 1) {
8214                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8215                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8216                      if(canAdjudicate && appData.checkMates) {
8217                          if(engineOpponent)
8218                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8219                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8220                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8221                          return 1;
8222                      }
8223                   } else
8224                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8225                   {    /* bare King */
8226                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8227                         if(canAdjudicate && appData.checkMates) {
8228                             /* but only adjudicate if adjudication enabled */
8229                             if(engineOpponent)
8230                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8231                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8232                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8233                             return 1;
8234                         }
8235                   }
8236                 } else bare = 1;
8237
8238
8239             // don't wait for engine to announce game end if we can judge ourselves
8240             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8241               case MT_CHECK:
8242                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8243                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8244                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8245                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8246                             checkCnt++;
8247                         if(checkCnt >= 2) {
8248                             reason = "Xboard adjudication: 3rd check";
8249                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8250                             break;
8251                         }
8252                     }
8253                 }
8254               case MT_NONE:
8255               default:
8256                 break;
8257               case MT_STEALMATE:
8258               case MT_STALEMATE:
8259               case MT_STAINMATE:
8260                 reason = "Xboard adjudication: Stalemate";
8261                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8262                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8263                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8264                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8265                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8266                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8267                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8268                                                                         EP_CHECKMATE : EP_WINS);
8269                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8270                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8271                 }
8272                 break;
8273               case MT_CHECKMATE:
8274                 reason = "Xboard adjudication: Checkmate";
8275                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8276                 if(gameInfo.variant == VariantShogi) {
8277                     if(forwardMostMove > backwardMostMove
8278                        && moveList[forwardMostMove-1][1] == '@'
8279                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8280                         reason = "XBoard adjudication: pawn-drop mate";
8281                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8282                     }
8283                 }
8284                 break;
8285             }
8286
8287                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8288                     case EP_STALEMATE:
8289                         result = GameIsDrawn; break;
8290                     case EP_CHECKMATE:
8291                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8292                     case EP_WINS:
8293                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8294                     default:
8295                         result = EndOfFile;
8296                 }
8297                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8298                     if(engineOpponent)
8299                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8300                     GameEnds( result, reason, GE_XBOARD );
8301                     return 1;
8302                 }
8303
8304                 /* Next absolutely insufficient mating material. */
8305                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8306                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8307                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8308
8309                      /* always flag draws, for judging claims */
8310                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8311
8312                      if(canAdjudicate && appData.materialDraws) {
8313                          /* but only adjudicate them if adjudication enabled */
8314                          if(engineOpponent) {
8315                            SendToProgram("force\n", engineOpponent); // suppress reply
8316                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8317                          }
8318                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8319                          return 1;
8320                      }
8321                 }
8322
8323                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8324                 if(gameInfo.variant == VariantXiangqi ?
8325                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8326                  : nrW + nrB == 4 &&
8327                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8328                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8329                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8330                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8331                    ) ) {
8332                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8333                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8334                           if(engineOpponent) {
8335                             SendToProgram("force\n", engineOpponent); // suppress reply
8336                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8337                           }
8338                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8339                           return 1;
8340                      }
8341                 } else moveCount = 6;
8342             }
8343
8344         // Repetition draws and 50-move rule can be applied independently of legality testing
8345
8346                 /* Check for rep-draws */
8347                 count = 0;
8348                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8349                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8350                 for(k = forwardMostMove-2;
8351                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8352                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8353                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8354                     k-=2)
8355                 {   int rights=0;
8356                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8357                         /* compare castling rights */
8358                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8359                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8360                                 rights++; /* King lost rights, while rook still had them */
8361                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8362                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8363                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8364                                    rights++; /* but at least one rook lost them */
8365                         }
8366                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8367                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8368                                 rights++;
8369                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8370                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8371                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8372                                    rights++;
8373                         }
8374                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8375                             && appData.drawRepeats > 1) {
8376                              /* adjudicate after user-specified nr of repeats */
8377                              int result = GameIsDrawn;
8378                              char *details = "XBoard adjudication: repetition draw";
8379                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8380                                 // [HGM] xiangqi: check for forbidden perpetuals
8381                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8382                                 for(m=forwardMostMove; m>k; m-=2) {
8383                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8384                                         ourPerpetual = 0; // the current mover did not always check
8385                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8386                                         hisPerpetual = 0; // the opponent did not always check
8387                                 }
8388                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8389                                                                         ourPerpetual, hisPerpetual);
8390                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8391                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8392                                     details = "Xboard adjudication: perpetual checking";
8393                                 } else
8394                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8395                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8396                                 } else
8397                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8398                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8399                                         result = BlackWins;
8400                                         details = "Xboard adjudication: repetition";
8401                                     }
8402                                 } else // it must be XQ
8403                                 // Now check for perpetual chases
8404                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8405                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8406                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8407                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8408                                         static char resdet[MSG_SIZ];
8409                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8410                                         details = resdet;
8411                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8412                                     } else
8413                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8414                                         break; // Abort repetition-checking loop.
8415                                 }
8416                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8417                              }
8418                              if(engineOpponent) {
8419                                SendToProgram("force\n", engineOpponent); // suppress reply
8420                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8421                              }
8422                              GameEnds( result, details, GE_XBOARD );
8423                              return 1;
8424                         }
8425                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8426                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8427                     }
8428                 }
8429
8430                 /* Now we test for 50-move draws. Determine ply count */
8431                 count = forwardMostMove;
8432                 /* look for last irreversble move */
8433                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8434                     count--;
8435                 /* if we hit starting position, add initial plies */
8436                 if( count == backwardMostMove )
8437                     count -= initialRulePlies;
8438                 count = forwardMostMove - count;
8439                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8440                         // adjust reversible move counter for checks in Xiangqi
8441                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8442                         if(i < backwardMostMove) i = backwardMostMove;
8443                         while(i <= forwardMostMove) {
8444                                 lastCheck = inCheck; // check evasion does not count
8445                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8446                                 if(inCheck || lastCheck) count--; // check does not count
8447                                 i++;
8448                         }
8449                 }
8450                 if( count >= 100)
8451                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8452                          /* this is used to judge if draw claims are legal */
8453                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8454                          if(engineOpponent) {
8455                            SendToProgram("force\n", engineOpponent); // suppress reply
8456                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8457                          }
8458                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8459                          return 1;
8460                 }
8461
8462                 /* if draw offer is pending, treat it as a draw claim
8463                  * when draw condition present, to allow engines a way to
8464                  * claim draws before making their move to avoid a race
8465                  * condition occurring after their move
8466                  */
8467                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8468                          char *p = NULL;
8469                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8470                              p = "Draw claim: 50-move rule";
8471                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8472                              p = "Draw claim: 3-fold repetition";
8473                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8474                              p = "Draw claim: insufficient mating material";
8475                          if( p != NULL && canAdjudicate) {
8476                              if(engineOpponent) {
8477                                SendToProgram("force\n", engineOpponent); // suppress reply
8478                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8479                              }
8480                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8481                              return 1;
8482                          }
8483                 }
8484
8485                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8486                     if(engineOpponent) {
8487                       SendToProgram("force\n", engineOpponent); // suppress reply
8488                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8489                     }
8490                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8491                     return 1;
8492                 }
8493         return 0;
8494 }
8495
8496 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8497 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8498 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8499
8500 static int
8501 BitbaseProbe ()
8502 {
8503     int pieces[10], squares[10], cnt=0, r, f, res;
8504     static int loaded;
8505     static PPROBE_EGBB probeBB;
8506     if(!appData.testLegality) return 10;
8507     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8508     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8509     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8510     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8511         ChessSquare piece = boards[forwardMostMove][r][f];
8512         int black = (piece >= BlackPawn);
8513         int type = piece - black*BlackPawn;
8514         if(piece == EmptySquare) continue;
8515         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8516         if(type == WhiteKing) type = WhiteQueen + 1;
8517         type = egbbCode[type];
8518         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8519         pieces[cnt] = type + black*6;
8520         if(++cnt > 5) return 11;
8521     }
8522     pieces[cnt] = squares[cnt] = 0;
8523     // probe EGBB
8524     if(loaded == 2) return 13; // loading failed before
8525     if(loaded == 0) {
8526         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8527         HMODULE lib;
8528         PLOAD_EGBB loadBB;
8529         loaded = 2; // prepare for failure
8530         if(!path) return 13; // no egbb installed
8531         strncpy(buf, path + 8, MSG_SIZ);
8532         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8533         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8534         lib = LoadLibrary(buf);
8535         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8536         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8537         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8538         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8539         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8540         loaded = 1; // success!
8541     }
8542     res = probeBB(forwardMostMove & 1, pieces, squares);
8543     return res > 0 ? 1 : res < 0 ? -1 : 0;
8544 }
8545
8546 char *
8547 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8548 {   // [HGM] book: this routine intercepts moves to simulate book replies
8549     char *bookHit = NULL;
8550
8551     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8552         char buf[MSG_SIZ];
8553         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8554         SendToProgram(buf, cps);
8555     }
8556     //first determine if the incoming move brings opponent into his book
8557     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8558         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8559     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8560     if(bookHit != NULL && !cps->bookSuspend) {
8561         // make sure opponent is not going to reply after receiving move to book position
8562         SendToProgram("force\n", cps);
8563         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8564     }
8565     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8566     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8567     // now arrange restart after book miss
8568     if(bookHit) {
8569         // after a book hit we never send 'go', and the code after the call to this routine
8570         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8571         char buf[MSG_SIZ], *move = bookHit;
8572         if(cps->useSAN) {
8573             int fromX, fromY, toX, toY;
8574             char promoChar;
8575             ChessMove moveType;
8576             move = buf + 30;
8577             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8578                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8579                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8580                                     PosFlags(forwardMostMove),
8581                                     fromY, fromX, toY, toX, promoChar, move);
8582             } else {
8583                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8584                 bookHit = NULL;
8585             }
8586         }
8587         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8588         SendToProgram(buf, cps);
8589         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8590     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8591         SendToProgram("go\n", cps);
8592         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8593     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8594         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8595             SendToProgram("go\n", cps);
8596         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8597     }
8598     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8599 }
8600
8601 int
8602 LoadError (char *errmess, ChessProgramState *cps)
8603 {   // unloads engine and switches back to -ncp mode if it was first
8604     if(cps->initDone) return FALSE;
8605     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8606     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8607     cps->pr = NoProc;
8608     if(cps == &first) {
8609         appData.noChessProgram = TRUE;
8610         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8611         gameMode = BeginningOfGame; ModeHighlight();
8612         SetNCPMode();
8613     }
8614     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8615     DisplayMessage("", ""); // erase waiting message
8616     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8617     return TRUE;
8618 }
8619
8620 char *savedMessage;
8621 ChessProgramState *savedState;
8622 void
8623 DeferredBookMove (void)
8624 {
8625         if(savedState->lastPing != savedState->lastPong)
8626                     ScheduleDelayedEvent(DeferredBookMove, 10);
8627         else
8628         HandleMachineMove(savedMessage, savedState);
8629 }
8630
8631 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8632 static ChessProgramState *stalledEngine;
8633 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8634
8635 void
8636 HandleMachineMove (char *message, ChessProgramState *cps)
8637 {
8638     static char firstLeg[20];
8639     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8640     char realname[MSG_SIZ];
8641     int fromX, fromY, toX, toY;
8642     ChessMove moveType;
8643     char promoChar, roar;
8644     char *p, *pv=buf1;
8645     int oldError;
8646     char *bookHit;
8647
8648     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8649         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8650         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8651             DisplayError(_("Invalid pairing from pairing engine"), 0);
8652             return;
8653         }
8654         pairingReceived = 1;
8655         NextMatchGame();
8656         return; // Skim the pairing messages here.
8657     }
8658
8659     oldError = cps->userError; cps->userError = 0;
8660
8661 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8662     /*
8663      * Kludge to ignore BEL characters
8664      */
8665     while (*message == '\007') message++;
8666
8667     /*
8668      * [HGM] engine debug message: ignore lines starting with '#' character
8669      */
8670     if(cps->debug && *message == '#') return;
8671
8672     /*
8673      * Look for book output
8674      */
8675     if (cps == &first && bookRequested) {
8676         if (message[0] == '\t' || message[0] == ' ') {
8677             /* Part of the book output is here; append it */
8678             strcat(bookOutput, message);
8679             strcat(bookOutput, "  \n");
8680             return;
8681         } else if (bookOutput[0] != NULLCHAR) {
8682             /* All of book output has arrived; display it */
8683             char *p = bookOutput;
8684             while (*p != NULLCHAR) {
8685                 if (*p == '\t') *p = ' ';
8686                 p++;
8687             }
8688             DisplayInformation(bookOutput);
8689             bookRequested = FALSE;
8690             /* Fall through to parse the current output */
8691         }
8692     }
8693
8694     /*
8695      * Look for machine move.
8696      */
8697     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8698         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8699     {
8700         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8701             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8702             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8703             stalledEngine = cps;
8704             if(appData.ponderNextMove) { // bring opponent out of ponder
8705                 if(gameMode == TwoMachinesPlay) {
8706                     if(cps->other->pause)
8707                         PauseEngine(cps->other);
8708                     else
8709                         SendToProgram("easy\n", cps->other);
8710                 }
8711             }
8712             StopClocks();
8713             return;
8714         }
8715
8716       if(cps->usePing) {
8717
8718         /* This method is only useful on engines that support ping */
8719         if(abortEngineThink) {
8720             if (appData.debugMode) {
8721                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8722             }
8723             SendToProgram("undo\n", cps);
8724             return;
8725         }
8726
8727         if (cps->lastPing != cps->lastPong) {
8728             /* Extra move from before last new; ignore */
8729             if (appData.debugMode) {
8730                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8731             }
8732           return;
8733         }
8734
8735       } else {
8736
8737         int machineWhite = FALSE;
8738
8739         switch (gameMode) {
8740           case BeginningOfGame:
8741             /* Extra move from before last reset; ignore */
8742             if (appData.debugMode) {
8743                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8744             }
8745             return;
8746
8747           case EndOfGame:
8748           case IcsIdle:
8749           default:
8750             /* Extra move after we tried to stop.  The mode test is
8751                not a reliable way of detecting this problem, but it's
8752                the best we can do on engines that don't support ping.
8753             */
8754             if (appData.debugMode) {
8755                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8756                         cps->which, gameMode);
8757             }
8758             SendToProgram("undo\n", cps);
8759             return;
8760
8761           case MachinePlaysWhite:
8762           case IcsPlayingWhite:
8763             machineWhite = TRUE;
8764             break;
8765
8766           case MachinePlaysBlack:
8767           case IcsPlayingBlack:
8768             machineWhite = FALSE;
8769             break;
8770
8771           case TwoMachinesPlay:
8772             machineWhite = (cps->twoMachinesColor[0] == 'w');
8773             break;
8774         }
8775         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8776             if (appData.debugMode) {
8777                 fprintf(debugFP,
8778                         "Ignoring move out of turn by %s, gameMode %d"
8779                         ", forwardMost %d\n",
8780                         cps->which, gameMode, forwardMostMove);
8781             }
8782             return;
8783         }
8784       }
8785
8786         if(cps->alphaRank) AlphaRank(machineMove, 4);
8787
8788         // [HGM] lion: (some very limited) support for Alien protocol
8789         killX = killY = kill2X = kill2Y = -1;
8790         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8791             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8792             return;
8793         }
8794         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8795             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8796             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8797         }
8798         if(firstLeg[0]) { // there was a previous leg;
8799             // only support case where same piece makes two step
8800             char buf[20], *p = machineMove+1, *q = buf+1, f;
8801             safeStrCpy(buf, machineMove, 20);
8802             while(isdigit(*q)) q++; // find start of to-square
8803             safeStrCpy(machineMove, firstLeg, 20);
8804             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8805             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8806             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8807             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8808             firstLeg[0] = NULLCHAR;
8809         }
8810
8811         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8812                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8813             /* Machine move could not be parsed; ignore it. */
8814           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8815                     machineMove, _(cps->which));
8816             DisplayMoveError(buf1);
8817             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8818                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8819             if (gameMode == TwoMachinesPlay) {
8820               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8821                        buf1, GE_XBOARD);
8822             }
8823             return;
8824         }
8825
8826         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8827         /* So we have to redo legality test with true e.p. status here,  */
8828         /* to make sure an illegal e.p. capture does not slip through,   */
8829         /* to cause a forfeit on a justified illegal-move complaint      */
8830         /* of the opponent.                                              */
8831         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8832            ChessMove moveType;
8833            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8834                              fromY, fromX, toY, toX, promoChar);
8835             if(moveType == IllegalMove) {
8836               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8837                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8838                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8839                            buf1, GE_XBOARD);
8840                 return;
8841            } else if(!appData.fischerCastling)
8842            /* [HGM] Kludge to handle engines that send FRC-style castling
8843               when they shouldn't (like TSCP-Gothic) */
8844            switch(moveType) {
8845              case WhiteASideCastleFR:
8846              case BlackASideCastleFR:
8847                toX+=2;
8848                currentMoveString[2]++;
8849                break;
8850              case WhiteHSideCastleFR:
8851              case BlackHSideCastleFR:
8852                toX--;
8853                currentMoveString[2]--;
8854                break;
8855              default: ; // nothing to do, but suppresses warning of pedantic compilers
8856            }
8857         }
8858         hintRequested = FALSE;
8859         lastHint[0] = NULLCHAR;
8860         bookRequested = FALSE;
8861         /* Program may be pondering now */
8862         cps->maybeThinking = TRUE;
8863         if (cps->sendTime == 2) cps->sendTime = 1;
8864         if (cps->offeredDraw) cps->offeredDraw--;
8865
8866         /* [AS] Save move info*/
8867         pvInfoList[ forwardMostMove ].score = programStats.score;
8868         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8869         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8870
8871         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8872
8873         /* Test suites abort the 'game' after one move */
8874         if(*appData.finger) {
8875            static FILE *f;
8876            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8877            if(!f) f = fopen(appData.finger, "w");
8878            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8879            else { DisplayFatalError("Bad output file", errno, 0); return; }
8880            free(fen);
8881            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8882         }
8883         if(appData.epd) {
8884            if(solvingTime >= 0) {
8885               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8886               totalTime += solvingTime; first.matchWins++;
8887            } else {
8888               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8889               second.matchWins++;
8890            }
8891            OutputKibitz(2, buf1);
8892            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8893         }
8894
8895         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8896         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8897             int count = 0;
8898
8899             while( count < adjudicateLossPlies ) {
8900                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8901
8902                 if( count & 1 ) {
8903                     score = -score; /* Flip score for winning side */
8904                 }
8905
8906                 if( score > appData.adjudicateLossThreshold ) {
8907                     break;
8908                 }
8909
8910                 count++;
8911             }
8912
8913             if( count >= adjudicateLossPlies ) {
8914                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8915
8916                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8917                     "Xboard adjudication",
8918                     GE_XBOARD );
8919
8920                 return;
8921             }
8922         }
8923
8924         if(Adjudicate(cps)) {
8925             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8926             return; // [HGM] adjudicate: for all automatic game ends
8927         }
8928
8929 #if ZIPPY
8930         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8931             first.initDone) {
8932           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8933                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8934                 SendToICS("draw ");
8935                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8936           }
8937           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8938           ics_user_moved = 1;
8939           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8940                 char buf[3*MSG_SIZ];
8941
8942                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8943                         programStats.score / 100.,
8944                         programStats.depth,
8945                         programStats.time / 100.,
8946                         (unsigned int)programStats.nodes,
8947                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8948                         programStats.movelist);
8949                 SendToICS(buf);
8950           }
8951         }
8952 #endif
8953
8954         /* [AS] Clear stats for next move */
8955         ClearProgramStats();
8956         thinkOutput[0] = NULLCHAR;
8957         hiddenThinkOutputState = 0;
8958
8959         bookHit = NULL;
8960         if (gameMode == TwoMachinesPlay) {
8961             /* [HGM] relaying draw offers moved to after reception of move */
8962             /* and interpreting offer as claim if it brings draw condition */
8963             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8964                 SendToProgram("draw\n", cps->other);
8965             }
8966             if (cps->other->sendTime) {
8967                 SendTimeRemaining(cps->other,
8968                                   cps->other->twoMachinesColor[0] == 'w');
8969             }
8970             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8971             if (firstMove && !bookHit) {
8972                 firstMove = FALSE;
8973                 if (cps->other->useColors) {
8974                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8975                 }
8976                 SendToProgram("go\n", cps->other);
8977             }
8978             cps->other->maybeThinking = TRUE;
8979         }
8980
8981         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8982
8983         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8984
8985         if (!pausing && appData.ringBellAfterMoves) {
8986             if(!roar) RingBell();
8987         }
8988
8989         /*
8990          * Reenable menu items that were disabled while
8991          * machine was thinking
8992          */
8993         if (gameMode != TwoMachinesPlay)
8994             SetUserThinkingEnables();
8995
8996         // [HGM] book: after book hit opponent has received move and is now in force mode
8997         // force the book reply into it, and then fake that it outputted this move by jumping
8998         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8999         if(bookHit) {
9000                 static char bookMove[MSG_SIZ]; // a bit generous?
9001
9002                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9003                 strcat(bookMove, bookHit);
9004                 message = bookMove;
9005                 cps = cps->other;
9006                 programStats.nodes = programStats.depth = programStats.time =
9007                 programStats.score = programStats.got_only_move = 0;
9008                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9009
9010                 if(cps->lastPing != cps->lastPong) {
9011                     savedMessage = message; // args for deferred call
9012                     savedState = cps;
9013                     ScheduleDelayedEvent(DeferredBookMove, 10);
9014                     return;
9015                 }
9016                 goto FakeBookMove;
9017         }
9018
9019         return;
9020     }
9021
9022     /* Set special modes for chess engines.  Later something general
9023      *  could be added here; for now there is just one kludge feature,
9024      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9025      *  when "xboard" is given as an interactive command.
9026      */
9027     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9028         cps->useSigint = FALSE;
9029         cps->useSigterm = FALSE;
9030     }
9031     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9032       ParseFeatures(message+8, cps);
9033       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9034     }
9035
9036     if (!strncmp(message, "setup ", 6) && 
9037         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9038           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9039                                         ) { // [HGM] allow first engine to define opening position
9040       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9041       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9042       *buf = NULLCHAR;
9043       if(sscanf(message, "setup (%s", buf) == 1) {
9044         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9045         ASSIGN(appData.pieceToCharTable, buf);
9046       }
9047       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9048       if(dummy >= 3) {
9049         while(message[s] && message[s++] != ' ');
9050         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9051            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9052             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9053             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9054           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9055           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9056           startedFromSetupPosition = FALSE;
9057         }
9058       }
9059       if(startedFromSetupPosition) return;
9060       ParseFEN(boards[0], &dummy, message+s, FALSE);
9061       DrawPosition(TRUE, boards[0]);
9062       CopyBoard(initialPosition, boards[0]);
9063       startedFromSetupPosition = TRUE;
9064       return;
9065     }
9066     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9067       ChessSquare piece = WhitePawn;
9068       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9069       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9070       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9071       piece += CharToPiece(ID & 255) - WhitePawn;
9072       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9073       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9074       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9075       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9076       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9077       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9078                                                && gameInfo.variant != VariantGreat
9079                                                && gameInfo.variant != VariantFairy    ) return;
9080       if(piece < EmptySquare) {
9081         pieceDefs = TRUE;
9082         ASSIGN(pieceDesc[piece], buf1);
9083         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9084       }
9085       return;
9086     }
9087     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9088       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9089       Sweep(0);
9090       return;
9091     }
9092     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9093      * want this, I was asked to put it in, and obliged.
9094      */
9095     if (!strncmp(message, "setboard ", 9)) {
9096         Board initial_position;
9097
9098         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9099
9100         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9101             DisplayError(_("Bad FEN received from engine"), 0);
9102             return ;
9103         } else {
9104            Reset(TRUE, FALSE);
9105            CopyBoard(boards[0], initial_position);
9106            initialRulePlies = FENrulePlies;
9107            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9108            else gameMode = MachinePlaysBlack;
9109            DrawPosition(FALSE, boards[currentMove]);
9110         }
9111         return;
9112     }
9113
9114     /*
9115      * Look for communication commands
9116      */
9117     if (!strncmp(message, "telluser ", 9)) {
9118         if(message[9] == '\\' && message[10] == '\\')
9119             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9120         PlayTellSound();
9121         DisplayNote(message + 9);
9122         return;
9123     }
9124     if (!strncmp(message, "tellusererror ", 14)) {
9125         cps->userError = 1;
9126         if(message[14] == '\\' && message[15] == '\\')
9127             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9128         PlayTellSound();
9129         DisplayError(message + 14, 0);
9130         return;
9131     }
9132     if (!strncmp(message, "tellopponent ", 13)) {
9133       if (appData.icsActive) {
9134         if (loggedOn) {
9135           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9136           SendToICS(buf1);
9137         }
9138       } else {
9139         DisplayNote(message + 13);
9140       }
9141       return;
9142     }
9143     if (!strncmp(message, "tellothers ", 11)) {
9144       if (appData.icsActive) {
9145         if (loggedOn) {
9146           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9147           SendToICS(buf1);
9148         }
9149       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9150       return;
9151     }
9152     if (!strncmp(message, "tellall ", 8)) {
9153       if (appData.icsActive) {
9154         if (loggedOn) {
9155           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9156           SendToICS(buf1);
9157         }
9158       } else {
9159         DisplayNote(message + 8);
9160       }
9161       return;
9162     }
9163     if (strncmp(message, "warning", 7) == 0) {
9164         /* Undocumented feature, use tellusererror in new code */
9165         DisplayError(message, 0);
9166         return;
9167     }
9168     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9169         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9170         strcat(realname, " query");
9171         AskQuestion(realname, buf2, buf1, cps->pr);
9172         return;
9173     }
9174     /* Commands from the engine directly to ICS.  We don't allow these to be
9175      *  sent until we are logged on. Crafty kibitzes have been known to
9176      *  interfere with the login process.
9177      */
9178     if (loggedOn) {
9179         if (!strncmp(message, "tellics ", 8)) {
9180             SendToICS(message + 8);
9181             SendToICS("\n");
9182             return;
9183         }
9184         if (!strncmp(message, "tellicsnoalias ", 15)) {
9185             SendToICS(ics_prefix);
9186             SendToICS(message + 15);
9187             SendToICS("\n");
9188             return;
9189         }
9190         /* The following are for backward compatibility only */
9191         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9192             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9193             SendToICS(ics_prefix);
9194             SendToICS(message);
9195             SendToICS("\n");
9196             return;
9197         }
9198     }
9199     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9200         if(initPing == cps->lastPong) {
9201             if(gameInfo.variant == VariantUnknown) {
9202                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9203                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9204                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9205             }
9206             initPing = -1;
9207         }
9208         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9209             abortEngineThink = FALSE;
9210             DisplayMessage("", "");
9211             ThawUI();
9212         }
9213         return;
9214     }
9215     if(!strncmp(message, "highlight ", 10)) {
9216         if(appData.testLegality && !*engineVariant && appData.markers) return;
9217         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9218         return;
9219     }
9220     if(!strncmp(message, "click ", 6)) {
9221         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9222         if(appData.testLegality || !appData.oneClick) return;
9223         sscanf(message+6, "%c%d%c", &f, &y, &c);
9224         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9225         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9226         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9227         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9228         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9229         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9230             LeftClick(Release, lastLeftX, lastLeftY);
9231         controlKey  = (c == ',');
9232         LeftClick(Press, x, y);
9233         LeftClick(Release, x, y);
9234         first.highlight = f;
9235         return;
9236     }
9237     /*
9238      * If the move is illegal, cancel it and redraw the board.
9239      * Also deal with other error cases.  Matching is rather loose
9240      * here to accommodate engines written before the spec.
9241      */
9242     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9243         strncmp(message, "Error", 5) == 0) {
9244         if (StrStr(message, "name") ||
9245             StrStr(message, "rating") || StrStr(message, "?") ||
9246             StrStr(message, "result") || StrStr(message, "board") ||
9247             StrStr(message, "bk") || StrStr(message, "computer") ||
9248             StrStr(message, "variant") || StrStr(message, "hint") ||
9249             StrStr(message, "random") || StrStr(message, "depth") ||
9250             StrStr(message, "accepted")) {
9251             return;
9252         }
9253         if (StrStr(message, "protover")) {
9254           /* Program is responding to input, so it's apparently done
9255              initializing, and this error message indicates it is
9256              protocol version 1.  So we don't need to wait any longer
9257              for it to initialize and send feature commands. */
9258           FeatureDone(cps, 1);
9259           cps->protocolVersion = 1;
9260           return;
9261         }
9262         cps->maybeThinking = FALSE;
9263
9264         if (StrStr(message, "draw")) {
9265             /* Program doesn't have "draw" command */
9266             cps->sendDrawOffers = 0;
9267             return;
9268         }
9269         if (cps->sendTime != 1 &&
9270             (StrStr(message, "time") || StrStr(message, "otim"))) {
9271           /* Program apparently doesn't have "time" or "otim" command */
9272           cps->sendTime = 0;
9273           return;
9274         }
9275         if (StrStr(message, "analyze")) {
9276             cps->analysisSupport = FALSE;
9277             cps->analyzing = FALSE;
9278 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9279             EditGameEvent(); // [HGM] try to preserve loaded game
9280             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9281             DisplayError(buf2, 0);
9282             return;
9283         }
9284         if (StrStr(message, "(no matching move)st")) {
9285           /* Special kludge for GNU Chess 4 only */
9286           cps->stKludge = TRUE;
9287           SendTimeControl(cps, movesPerSession, timeControl,
9288                           timeIncrement, appData.searchDepth,
9289                           searchTime);
9290           return;
9291         }
9292         if (StrStr(message, "(no matching move)sd")) {
9293           /* Special kludge for GNU Chess 4 only */
9294           cps->sdKludge = TRUE;
9295           SendTimeControl(cps, movesPerSession, timeControl,
9296                           timeIncrement, appData.searchDepth,
9297                           searchTime);
9298           return;
9299         }
9300         if (!StrStr(message, "llegal")) {
9301             return;
9302         }
9303         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9304             gameMode == IcsIdle) return;
9305         if (forwardMostMove <= backwardMostMove) return;
9306         if (pausing) PauseEvent();
9307       if(appData.forceIllegal) {
9308             // [HGM] illegal: machine refused move; force position after move into it
9309           SendToProgram("force\n", cps);
9310           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9311                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9312                 // when black is to move, while there might be nothing on a2 or black
9313                 // might already have the move. So send the board as if white has the move.
9314                 // But first we must change the stm of the engine, as it refused the last move
9315                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9316                 if(WhiteOnMove(forwardMostMove)) {
9317                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9318                     SendBoard(cps, forwardMostMove); // kludgeless board
9319                 } else {
9320                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9321                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9322                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9323                 }
9324           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9325             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9326                  gameMode == TwoMachinesPlay)
9327               SendToProgram("go\n", cps);
9328             return;
9329       } else
9330         if (gameMode == PlayFromGameFile) {
9331             /* Stop reading this game file */
9332             gameMode = EditGame;
9333             ModeHighlight();
9334         }
9335         /* [HGM] illegal-move claim should forfeit game when Xboard */
9336         /* only passes fully legal moves                            */
9337         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9338             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9339                                 "False illegal-move claim", GE_XBOARD );
9340             return; // do not take back move we tested as valid
9341         }
9342         currentMove = forwardMostMove-1;
9343         DisplayMove(currentMove-1); /* before DisplayMoveError */
9344         SwitchClocks(forwardMostMove-1); // [HGM] race
9345         DisplayBothClocks();
9346         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9347                 parseList[currentMove], _(cps->which));
9348         DisplayMoveError(buf1);
9349         DrawPosition(FALSE, boards[currentMove]);
9350
9351         SetUserThinkingEnables();
9352         return;
9353     }
9354     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9355         /* Program has a broken "time" command that
9356            outputs a string not ending in newline.
9357            Don't use it. */
9358         cps->sendTime = 0;
9359     }
9360     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9361         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9362             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9363     }
9364
9365     /*
9366      * If chess program startup fails, exit with an error message.
9367      * Attempts to recover here are futile. [HGM] Well, we try anyway
9368      */
9369     if ((StrStr(message, "unknown host") != NULL)
9370         || (StrStr(message, "No remote directory") != NULL)
9371         || (StrStr(message, "not found") != NULL)
9372         || (StrStr(message, "No such file") != NULL)
9373         || (StrStr(message, "can't alloc") != NULL)
9374         || (StrStr(message, "Permission denied") != NULL)) {
9375
9376         cps->maybeThinking = FALSE;
9377         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9378                 _(cps->which), cps->program, cps->host, message);
9379         RemoveInputSource(cps->isr);
9380         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9381             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9382             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9383         }
9384         return;
9385     }
9386
9387     /*
9388      * Look for hint output
9389      */
9390     if (sscanf(message, "Hint: %s", buf1) == 1) {
9391         if (cps == &first && hintRequested) {
9392             hintRequested = FALSE;
9393             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9394                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9395                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9396                                     PosFlags(forwardMostMove),
9397                                     fromY, fromX, toY, toX, promoChar, buf1);
9398                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9399                 DisplayInformation(buf2);
9400             } else {
9401                 /* Hint move could not be parsed!? */
9402               snprintf(buf2, sizeof(buf2),
9403                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9404                         buf1, _(cps->which));
9405                 DisplayError(buf2, 0);
9406             }
9407         } else {
9408           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9409         }
9410         return;
9411     }
9412
9413     /*
9414      * Ignore other messages if game is not in progress
9415      */
9416     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9417         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9418
9419     /*
9420      * look for win, lose, draw, or draw offer
9421      */
9422     if (strncmp(message, "1-0", 3) == 0) {
9423         char *p, *q, *r = "";
9424         p = strchr(message, '{');
9425         if (p) {
9426             q = strchr(p, '}');
9427             if (q) {
9428                 *q = NULLCHAR;
9429                 r = p + 1;
9430             }
9431         }
9432         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9433         return;
9434     } else if (strncmp(message, "0-1", 3) == 0) {
9435         char *p, *q, *r = "";
9436         p = strchr(message, '{');
9437         if (p) {
9438             q = strchr(p, '}');
9439             if (q) {
9440                 *q = NULLCHAR;
9441                 r = p + 1;
9442             }
9443         }
9444         /* Kludge for Arasan 4.1 bug */
9445         if (strcmp(r, "Black resigns") == 0) {
9446             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9447             return;
9448         }
9449         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9450         return;
9451     } else if (strncmp(message, "1/2", 3) == 0) {
9452         char *p, *q, *r = "";
9453         p = strchr(message, '{');
9454         if (p) {
9455             q = strchr(p, '}');
9456             if (q) {
9457                 *q = NULLCHAR;
9458                 r = p + 1;
9459             }
9460         }
9461
9462         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9463         return;
9464
9465     } else if (strncmp(message, "White resign", 12) == 0) {
9466         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9467         return;
9468     } else if (strncmp(message, "Black resign", 12) == 0) {
9469         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9470         return;
9471     } else if (strncmp(message, "White matches", 13) == 0 ||
9472                strncmp(message, "Black matches", 13) == 0   ) {
9473         /* [HGM] ignore GNUShogi noises */
9474         return;
9475     } else if (strncmp(message, "White", 5) == 0 &&
9476                message[5] != '(' &&
9477                StrStr(message, "Black") == NULL) {
9478         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9479         return;
9480     } else if (strncmp(message, "Black", 5) == 0 &&
9481                message[5] != '(') {
9482         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9483         return;
9484     } else if (strcmp(message, "resign") == 0 ||
9485                strcmp(message, "computer resigns") == 0) {
9486         switch (gameMode) {
9487           case MachinePlaysBlack:
9488           case IcsPlayingBlack:
9489             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9490             break;
9491           case MachinePlaysWhite:
9492           case IcsPlayingWhite:
9493             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9494             break;
9495           case TwoMachinesPlay:
9496             if (cps->twoMachinesColor[0] == 'w')
9497               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9498             else
9499               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9500             break;
9501           default:
9502             /* can't happen */
9503             break;
9504         }
9505         return;
9506     } else if (strncmp(message, "opponent mates", 14) == 0) {
9507         switch (gameMode) {
9508           case MachinePlaysBlack:
9509           case IcsPlayingBlack:
9510             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9511             break;
9512           case MachinePlaysWhite:
9513           case IcsPlayingWhite:
9514             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9515             break;
9516           case TwoMachinesPlay:
9517             if (cps->twoMachinesColor[0] == 'w')
9518               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9519             else
9520               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9521             break;
9522           default:
9523             /* can't happen */
9524             break;
9525         }
9526         return;
9527     } else if (strncmp(message, "computer mates", 14) == 0) {
9528         switch (gameMode) {
9529           case MachinePlaysBlack:
9530           case IcsPlayingBlack:
9531             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9532             break;
9533           case MachinePlaysWhite:
9534           case IcsPlayingWhite:
9535             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9536             break;
9537           case TwoMachinesPlay:
9538             if (cps->twoMachinesColor[0] == 'w')
9539               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9540             else
9541               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9542             break;
9543           default:
9544             /* can't happen */
9545             break;
9546         }
9547         return;
9548     } else if (strncmp(message, "checkmate", 9) == 0) {
9549         if (WhiteOnMove(forwardMostMove)) {
9550             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9551         } else {
9552             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9553         }
9554         return;
9555     } else if (strstr(message, "Draw") != NULL ||
9556                strstr(message, "game is a draw") != NULL) {
9557         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9558         return;
9559     } else if (strstr(message, "offer") != NULL &&
9560                strstr(message, "draw") != NULL) {
9561 #if ZIPPY
9562         if (appData.zippyPlay && first.initDone) {
9563             /* Relay offer to ICS */
9564             SendToICS(ics_prefix);
9565             SendToICS("draw\n");
9566         }
9567 #endif
9568         cps->offeredDraw = 2; /* valid until this engine moves twice */
9569         if (gameMode == TwoMachinesPlay) {
9570             if (cps->other->offeredDraw) {
9571                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9572             /* [HGM] in two-machine mode we delay relaying draw offer      */
9573             /* until after we also have move, to see if it is really claim */
9574             }
9575         } else if (gameMode == MachinePlaysWhite ||
9576                    gameMode == MachinePlaysBlack) {
9577           if (userOfferedDraw) {
9578             DisplayInformation(_("Machine accepts your draw offer"));
9579             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9580           } else {
9581             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9582           }
9583         }
9584     }
9585
9586
9587     /*
9588      * Look for thinking output
9589      */
9590     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9591           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9592                                 ) {
9593         int plylev, mvleft, mvtot, curscore, time;
9594         char mvname[MOVE_LEN];
9595         u64 nodes; // [DM]
9596         char plyext;
9597         int ignore = FALSE;
9598         int prefixHint = FALSE;
9599         mvname[0] = NULLCHAR;
9600
9601         switch (gameMode) {
9602           case MachinePlaysBlack:
9603           case IcsPlayingBlack:
9604             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9605             break;
9606           case MachinePlaysWhite:
9607           case IcsPlayingWhite:
9608             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9609             break;
9610           case AnalyzeMode:
9611           case AnalyzeFile:
9612             break;
9613           case IcsObserving: /* [DM] icsEngineAnalyze */
9614             if (!appData.icsEngineAnalyze) ignore = TRUE;
9615             break;
9616           case TwoMachinesPlay:
9617             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9618                 ignore = TRUE;
9619             }
9620             break;
9621           default:
9622             ignore = TRUE;
9623             break;
9624         }
9625
9626         if (!ignore) {
9627             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9628             buf1[0] = NULLCHAR;
9629             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9630                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9631                 char score_buf[MSG_SIZ];
9632
9633                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9634                     nodes += u64Const(0x100000000);
9635
9636                 if (plyext != ' ' && plyext != '\t') {
9637                     time *= 100;
9638                 }
9639
9640                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9641                 if( cps->scoreIsAbsolute &&
9642                     ( gameMode == MachinePlaysBlack ||
9643                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9644                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9645                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9646                      !WhiteOnMove(currentMove)
9647                     ) )
9648                 {
9649                     curscore = -curscore;
9650                 }
9651
9652                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9653
9654                 if(*bestMove) { // rememer time best EPD move was first found
9655                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9656                     ChessMove mt;
9657                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9658                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9659                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9660                 }
9661
9662                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9663                         char buf[MSG_SIZ];
9664                         FILE *f;
9665                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9666                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9667                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9668                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9669                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9670                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9671                                 fclose(f);
9672                         }
9673                         else
9674                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9675                           DisplayError(_("failed writing PV"), 0);
9676                 }
9677
9678                 tempStats.depth = plylev;
9679                 tempStats.nodes = nodes;
9680                 tempStats.time = time;
9681                 tempStats.score = curscore;
9682                 tempStats.got_only_move = 0;
9683
9684                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9685                         int ticklen;
9686
9687                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9688                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9689                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9690                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9691                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9692                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9693                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9694                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9695                 }
9696
9697                 /* Buffer overflow protection */
9698                 if (pv[0] != NULLCHAR) {
9699                     if (strlen(pv) >= sizeof(tempStats.movelist)
9700                         && appData.debugMode) {
9701                         fprintf(debugFP,
9702                                 "PV is too long; using the first %u bytes.\n",
9703                                 (unsigned) sizeof(tempStats.movelist) - 1);
9704                     }
9705
9706                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9707                 } else {
9708                     sprintf(tempStats.movelist, " no PV\n");
9709                 }
9710
9711                 if (tempStats.seen_stat) {
9712                     tempStats.ok_to_send = 1;
9713                 }
9714
9715                 if (strchr(tempStats.movelist, '(') != NULL) {
9716                     tempStats.line_is_book = 1;
9717                     tempStats.nr_moves = 0;
9718                     tempStats.moves_left = 0;
9719                 } else {
9720                     tempStats.line_is_book = 0;
9721                 }
9722
9723                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9724                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9725
9726                 SendProgramStatsToFrontend( cps, &tempStats );
9727
9728                 /*
9729                     [AS] Protect the thinkOutput buffer from overflow... this
9730                     is only useful if buf1 hasn't overflowed first!
9731                 */
9732                 if(curscore >= MATE_SCORE) 
9733                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9734                 else if(curscore <= -MATE_SCORE) 
9735                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9736                 else
9737                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9738                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9739                          plylev,
9740                          (gameMode == TwoMachinesPlay ?
9741                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9742                          score_buf,
9743                          prefixHint ? lastHint : "",
9744                          prefixHint ? " " : "" );
9745
9746                 if( buf1[0] != NULLCHAR ) {
9747                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9748
9749                     if( strlen(pv) > max_len ) {
9750                         if( appData.debugMode) {
9751                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9752                         }
9753                         pv[max_len+1] = '\0';
9754                     }
9755
9756                     strcat( thinkOutput, pv);
9757                 }
9758
9759                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9760                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9761                     DisplayMove(currentMove - 1);
9762                 }
9763                 return;
9764
9765             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9766                 /* crafty (9.25+) says "(only move) <move>"
9767                  * if there is only 1 legal move
9768                  */
9769                 sscanf(p, "(only move) %s", buf1);
9770                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9771                 sprintf(programStats.movelist, "%s (only move)", buf1);
9772                 programStats.depth = 1;
9773                 programStats.nr_moves = 1;
9774                 programStats.moves_left = 1;
9775                 programStats.nodes = 1;
9776                 programStats.time = 1;
9777                 programStats.got_only_move = 1;
9778
9779                 /* Not really, but we also use this member to
9780                    mean "line isn't going to change" (Crafty
9781                    isn't searching, so stats won't change) */
9782                 programStats.line_is_book = 1;
9783
9784                 SendProgramStatsToFrontend( cps, &programStats );
9785
9786                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9787                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9788                     DisplayMove(currentMove - 1);
9789                 }
9790                 return;
9791             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9792                               &time, &nodes, &plylev, &mvleft,
9793                               &mvtot, mvname) >= 5) {
9794                 /* The stat01: line is from Crafty (9.29+) in response
9795                    to the "." command */
9796                 programStats.seen_stat = 1;
9797                 cps->maybeThinking = TRUE;
9798
9799                 if (programStats.got_only_move || !appData.periodicUpdates)
9800                   return;
9801
9802                 programStats.depth = plylev;
9803                 programStats.time = time;
9804                 programStats.nodes = nodes;
9805                 programStats.moves_left = mvleft;
9806                 programStats.nr_moves = mvtot;
9807                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9808                 programStats.ok_to_send = 1;
9809                 programStats.movelist[0] = '\0';
9810
9811                 SendProgramStatsToFrontend( cps, &programStats );
9812
9813                 return;
9814
9815             } else if (strncmp(message,"++",2) == 0) {
9816                 /* Crafty 9.29+ outputs this */
9817                 programStats.got_fail = 2;
9818                 return;
9819
9820             } else if (strncmp(message,"--",2) == 0) {
9821                 /* Crafty 9.29+ outputs this */
9822                 programStats.got_fail = 1;
9823                 return;
9824
9825             } else if (thinkOutput[0] != NULLCHAR &&
9826                        strncmp(message, "    ", 4) == 0) {
9827                 unsigned message_len;
9828
9829                 p = message;
9830                 while (*p && *p == ' ') p++;
9831
9832                 message_len = strlen( p );
9833
9834                 /* [AS] Avoid buffer overflow */
9835                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9836                     strcat(thinkOutput, " ");
9837                     strcat(thinkOutput, p);
9838                 }
9839
9840                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9841                     strcat(programStats.movelist, " ");
9842                     strcat(programStats.movelist, p);
9843                 }
9844
9845                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9846                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9847                     DisplayMove(currentMove - 1);
9848                 }
9849                 return;
9850             }
9851         }
9852         else {
9853             buf1[0] = NULLCHAR;
9854
9855             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9856                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9857             {
9858                 ChessProgramStats cpstats;
9859
9860                 if (plyext != ' ' && plyext != '\t') {
9861                     time *= 100;
9862                 }
9863
9864                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9865                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9866                     curscore = -curscore;
9867                 }
9868
9869                 cpstats.depth = plylev;
9870                 cpstats.nodes = nodes;
9871                 cpstats.time = time;
9872                 cpstats.score = curscore;
9873                 cpstats.got_only_move = 0;
9874                 cpstats.movelist[0] = '\0';
9875
9876                 if (buf1[0] != NULLCHAR) {
9877                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9878                 }
9879
9880                 cpstats.ok_to_send = 0;
9881                 cpstats.line_is_book = 0;
9882                 cpstats.nr_moves = 0;
9883                 cpstats.moves_left = 0;
9884
9885                 SendProgramStatsToFrontend( cps, &cpstats );
9886             }
9887         }
9888     }
9889 }
9890
9891
9892 /* Parse a game score from the character string "game", and
9893    record it as the history of the current game.  The game
9894    score is NOT assumed to start from the standard position.
9895    The display is not updated in any way.
9896    */
9897 void
9898 ParseGameHistory (char *game)
9899 {
9900     ChessMove moveType;
9901     int fromX, fromY, toX, toY, boardIndex;
9902     char promoChar;
9903     char *p, *q;
9904     char buf[MSG_SIZ];
9905
9906     if (appData.debugMode)
9907       fprintf(debugFP, "Parsing game history: %s\n", game);
9908
9909     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9910     gameInfo.site = StrSave(appData.icsHost);
9911     gameInfo.date = PGNDate();
9912     gameInfo.round = StrSave("-");
9913
9914     /* Parse out names of players */
9915     while (*game == ' ') game++;
9916     p = buf;
9917     while (*game != ' ') *p++ = *game++;
9918     *p = NULLCHAR;
9919     gameInfo.white = StrSave(buf);
9920     while (*game == ' ') game++;
9921     p = buf;
9922     while (*game != ' ' && *game != '\n') *p++ = *game++;
9923     *p = NULLCHAR;
9924     gameInfo.black = StrSave(buf);
9925
9926     /* Parse moves */
9927     boardIndex = blackPlaysFirst ? 1 : 0;
9928     yynewstr(game);
9929     for (;;) {
9930         yyboardindex = boardIndex;
9931         moveType = (ChessMove) Myylex();
9932         switch (moveType) {
9933           case IllegalMove:             /* maybe suicide chess, etc. */
9934   if (appData.debugMode) {
9935     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9936     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9937     setbuf(debugFP, NULL);
9938   }
9939           case WhitePromotion:
9940           case BlackPromotion:
9941           case WhiteNonPromotion:
9942           case BlackNonPromotion:
9943           case NormalMove:
9944           case FirstLeg:
9945           case WhiteCapturesEnPassant:
9946           case BlackCapturesEnPassant:
9947           case WhiteKingSideCastle:
9948           case WhiteQueenSideCastle:
9949           case BlackKingSideCastle:
9950           case BlackQueenSideCastle:
9951           case WhiteKingSideCastleWild:
9952           case WhiteQueenSideCastleWild:
9953           case BlackKingSideCastleWild:
9954           case BlackQueenSideCastleWild:
9955           /* PUSH Fabien */
9956           case WhiteHSideCastleFR:
9957           case WhiteASideCastleFR:
9958           case BlackHSideCastleFR:
9959           case BlackASideCastleFR:
9960           /* POP Fabien */
9961             fromX = currentMoveString[0] - AAA;
9962             fromY = currentMoveString[1] - ONE;
9963             toX = currentMoveString[2] - AAA;
9964             toY = currentMoveString[3] - ONE;
9965             promoChar = currentMoveString[4];
9966             break;
9967           case WhiteDrop:
9968           case BlackDrop:
9969             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9970             fromX = moveType == WhiteDrop ?
9971               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9972             (int) CharToPiece(ToLower(currentMoveString[0]));
9973             fromY = DROP_RANK;
9974             toX = currentMoveString[2] - AAA;
9975             toY = currentMoveString[3] - ONE;
9976             promoChar = NULLCHAR;
9977             break;
9978           case AmbiguousMove:
9979             /* bug? */
9980             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9981   if (appData.debugMode) {
9982     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9983     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9984     setbuf(debugFP, NULL);
9985   }
9986             DisplayError(buf, 0);
9987             return;
9988           case ImpossibleMove:
9989             /* bug? */
9990             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9991   if (appData.debugMode) {
9992     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9993     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9994     setbuf(debugFP, NULL);
9995   }
9996             DisplayError(buf, 0);
9997             return;
9998           case EndOfFile:
9999             if (boardIndex < backwardMostMove) {
10000                 /* Oops, gap.  How did that happen? */
10001                 DisplayError(_("Gap in move list"), 0);
10002                 return;
10003             }
10004             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10005             if (boardIndex > forwardMostMove) {
10006                 forwardMostMove = boardIndex;
10007             }
10008             return;
10009           case ElapsedTime:
10010             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10011                 strcat(parseList[boardIndex-1], " ");
10012                 strcat(parseList[boardIndex-1], yy_text);
10013             }
10014             continue;
10015           case Comment:
10016           case PGNTag:
10017           case NAG:
10018           default:
10019             /* ignore */
10020             continue;
10021           case WhiteWins:
10022           case BlackWins:
10023           case GameIsDrawn:
10024           case GameUnfinished:
10025             if (gameMode == IcsExamining) {
10026                 if (boardIndex < backwardMostMove) {
10027                     /* Oops, gap.  How did that happen? */
10028                     return;
10029                 }
10030                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10031                 return;
10032             }
10033             gameInfo.result = moveType;
10034             p = strchr(yy_text, '{');
10035             if (p == NULL) p = strchr(yy_text, '(');
10036             if (p == NULL) {
10037                 p = yy_text;
10038                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10039             } else {
10040                 q = strchr(p, *p == '{' ? '}' : ')');
10041                 if (q != NULL) *q = NULLCHAR;
10042                 p++;
10043             }
10044             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10045             gameInfo.resultDetails = StrSave(p);
10046             continue;
10047         }
10048         if (boardIndex >= forwardMostMove &&
10049             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10050             backwardMostMove = blackPlaysFirst ? 1 : 0;
10051             return;
10052         }
10053         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10054                                  fromY, fromX, toY, toX, promoChar,
10055                                  parseList[boardIndex]);
10056         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10057         /* currentMoveString is set as a side-effect of yylex */
10058         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10059         strcat(moveList[boardIndex], "\n");
10060         boardIndex++;
10061         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10062         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10063           case MT_NONE:
10064           case MT_STALEMATE:
10065           default:
10066             break;
10067           case MT_CHECK:
10068             if(!IS_SHOGI(gameInfo.variant))
10069                 strcat(parseList[boardIndex - 1], "+");
10070             break;
10071           case MT_CHECKMATE:
10072           case MT_STAINMATE:
10073             strcat(parseList[boardIndex - 1], "#");
10074             break;
10075         }
10076     }
10077 }
10078
10079
10080 /* Apply a move to the given board  */
10081 void
10082 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10083 {
10084   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10085   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10086
10087     /* [HGM] compute & store e.p. status and castling rights for new position */
10088     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10089
10090       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10091       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10092       board[EP_STATUS] = EP_NONE;
10093       board[EP_FILE] = board[EP_RANK] = 100;
10094
10095   if (fromY == DROP_RANK) {
10096         /* must be first */
10097         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10098             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10099             return;
10100         }
10101         piece = board[toY][toX] = (ChessSquare) fromX;
10102   } else {
10103 //      ChessSquare victim;
10104       int i;
10105
10106       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10107 //           victim = board[killY][killX],
10108            killed = board[killY][killX],
10109            board[killY][killX] = EmptySquare,
10110            board[EP_STATUS] = EP_CAPTURE;
10111            if( kill2X >= 0 && kill2Y >= 0)
10112              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10113       }
10114
10115       if( board[toY][toX] != EmptySquare ) {
10116            board[EP_STATUS] = EP_CAPTURE;
10117            if( (fromX != toX || fromY != toY) && // not igui!
10118                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10119                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10120                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10121            }
10122       }
10123
10124       pawn = board[fromY][fromX];
10125       if( pawn == WhiteLance || pawn == BlackLance ) {
10126            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10127                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10128                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10129            }
10130       }
10131       if( pawn == WhitePawn ) {
10132            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10133                board[EP_STATUS] = EP_PAWN_MOVE;
10134            if( toY-fromY>=2) {
10135                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10136                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10137                         gameInfo.variant != VariantBerolina || toX < fromX)
10138                       board[EP_STATUS] = toX | berolina;
10139                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10140                         gameInfo.variant != VariantBerolina || toX > fromX)
10141                       board[EP_STATUS] = toX;
10142            }
10143       } else
10144       if( pawn == BlackPawn ) {
10145            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10146                board[EP_STATUS] = EP_PAWN_MOVE;
10147            if( toY-fromY<= -2) {
10148                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10149                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10150                         gameInfo.variant != VariantBerolina || toX < fromX)
10151                       board[EP_STATUS] = toX | berolina;
10152                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10153                         gameInfo.variant != VariantBerolina || toX > fromX)
10154                       board[EP_STATUS] = toX;
10155            }
10156        }
10157
10158        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10159        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10160        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10161        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10162
10163        for(i=0; i<nrCastlingRights; i++) {
10164            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10165               board[CASTLING][i] == toX   && castlingRank[i] == toY
10166              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10167        }
10168
10169        if(gameInfo.variant == VariantSChess) { // update virginity
10170            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10171            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10172            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10173            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10174        }
10175
10176      if (fromX == toX && fromY == toY) return;
10177
10178      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10179      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10180      if(gameInfo.variant == VariantKnightmate)
10181          king += (int) WhiteUnicorn - (int) WhiteKing;
10182
10183     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10184        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10185         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10186         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10187         board[EP_STATUS] = EP_NONE; // capture was fake!
10188     } else
10189     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10190         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10191         board[toY][toX] = piece;
10192         board[EP_STATUS] = EP_NONE; // capture was fake!
10193     } else
10194     /* Code added by Tord: */
10195     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10196     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10197         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10198       board[EP_STATUS] = EP_NONE; // capture was fake!
10199       board[fromY][fromX] = EmptySquare;
10200       board[toY][toX] = EmptySquare;
10201       if((toX > fromX) != (piece == WhiteRook)) {
10202         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10203       } else {
10204         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10205       }
10206     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10207                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10208       board[EP_STATUS] = EP_NONE;
10209       board[fromY][fromX] = EmptySquare;
10210       board[toY][toX] = EmptySquare;
10211       if((toX > fromX) != (piece == BlackRook)) {
10212         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10213       } else {
10214         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10215       }
10216     /* End of code added by Tord */
10217
10218     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10219         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10220         board[toY][toX] = piece;
10221     } else if (board[fromY][fromX] == king
10222         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10223         && toY == fromY && toX > fromX+1) {
10224         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10225         board[fromY][toX-1] = board[fromY][rookX];
10226         board[fromY][rookX] = EmptySquare;
10227         board[fromY][fromX] = EmptySquare;
10228         board[toY][toX] = king;
10229     } else if (board[fromY][fromX] == king
10230         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10231                && toY == fromY && toX < fromX-1) {
10232         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10233         board[fromY][toX+1] = board[fromY][rookX];
10234         board[fromY][rookX] = EmptySquare;
10235         board[fromY][fromX] = EmptySquare;
10236         board[toY][toX] = king;
10237     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10238                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10239                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10240                ) {
10241         /* white pawn promotion */
10242         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10243         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10244             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10245         board[fromY][fromX] = EmptySquare;
10246     } else if ((fromY >= BOARD_HEIGHT>>1)
10247                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10248                && (toX != fromX)
10249                && gameInfo.variant != VariantXiangqi
10250                && gameInfo.variant != VariantBerolina
10251                && (pawn == WhitePawn)
10252                && (board[toY][toX] == EmptySquare)) {
10253         board[fromY][fromX] = EmptySquare;
10254         board[toY][toX] = piece;
10255         if(toY == epRank - 128 + 1)
10256             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10257         else
10258             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10259     } else if ((fromY == BOARD_HEIGHT-4)
10260                && (toX == fromX)
10261                && gameInfo.variant == VariantBerolina
10262                && (board[fromY][fromX] == WhitePawn)
10263                && (board[toY][toX] == EmptySquare)) {
10264         board[fromY][fromX] = EmptySquare;
10265         board[toY][toX] = WhitePawn;
10266         if(oldEP & EP_BEROLIN_A) {
10267                 captured = board[fromY][fromX-1];
10268                 board[fromY][fromX-1] = EmptySquare;
10269         }else{  captured = board[fromY][fromX+1];
10270                 board[fromY][fromX+1] = EmptySquare;
10271         }
10272     } else if (board[fromY][fromX] == king
10273         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10274                && toY == fromY && toX > fromX+1) {
10275         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10276         board[fromY][toX-1] = board[fromY][rookX];
10277         board[fromY][rookX] = EmptySquare;
10278         board[fromY][fromX] = EmptySquare;
10279         board[toY][toX] = king;
10280     } else if (board[fromY][fromX] == king
10281         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10282                && toY == fromY && toX < fromX-1) {
10283         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10284         board[fromY][toX+1] = board[fromY][rookX];
10285         board[fromY][rookX] = EmptySquare;
10286         board[fromY][fromX] = EmptySquare;
10287         board[toY][toX] = king;
10288     } else if (fromY == 7 && fromX == 3
10289                && board[fromY][fromX] == BlackKing
10290                && toY == 7 && toX == 5) {
10291         board[fromY][fromX] = EmptySquare;
10292         board[toY][toX] = BlackKing;
10293         board[fromY][7] = EmptySquare;
10294         board[toY][4] = BlackRook;
10295     } else if (fromY == 7 && fromX == 3
10296                && board[fromY][fromX] == BlackKing
10297                && toY == 7 && toX == 1) {
10298         board[fromY][fromX] = EmptySquare;
10299         board[toY][toX] = BlackKing;
10300         board[fromY][0] = EmptySquare;
10301         board[toY][2] = BlackRook;
10302     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10303                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10304                && toY < promoRank && promoChar
10305                ) {
10306         /* black pawn promotion */
10307         board[toY][toX] = CharToPiece(ToLower(promoChar));
10308         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10309             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10310         board[fromY][fromX] = EmptySquare;
10311     } else if ((fromY < BOARD_HEIGHT>>1)
10312                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10313                && (toX != fromX)
10314                && gameInfo.variant != VariantXiangqi
10315                && gameInfo.variant != VariantBerolina
10316                && (pawn == BlackPawn)
10317                && (board[toY][toX] == EmptySquare)) {
10318         board[fromY][fromX] = EmptySquare;
10319         board[toY][toX] = piece;
10320         if(toY == epRank - 128 - 1)
10321             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10322         else
10323             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10324     } else if ((fromY == 3)
10325                && (toX == fromX)
10326                && gameInfo.variant == VariantBerolina
10327                && (board[fromY][fromX] == BlackPawn)
10328                && (board[toY][toX] == EmptySquare)) {
10329         board[fromY][fromX] = EmptySquare;
10330         board[toY][toX] = BlackPawn;
10331         if(oldEP & EP_BEROLIN_A) {
10332                 captured = board[fromY][fromX-1];
10333                 board[fromY][fromX-1] = EmptySquare;
10334         }else{  captured = board[fromY][fromX+1];
10335                 board[fromY][fromX+1] = EmptySquare;
10336         }
10337     } else {
10338         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10339         board[fromY][fromX] = EmptySquare;
10340         board[toY][toX] = piece;
10341     }
10342   }
10343
10344     if (gameInfo.holdingsWidth != 0) {
10345
10346       /* !!A lot more code needs to be written to support holdings  */
10347       /* [HGM] OK, so I have written it. Holdings are stored in the */
10348       /* penultimate board files, so they are automaticlly stored   */
10349       /* in the game history.                                       */
10350       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10351                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10352         /* Delete from holdings, by decreasing count */
10353         /* and erasing image if necessary            */
10354         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10355         if(p < (int) BlackPawn) { /* white drop */
10356              p -= (int)WhitePawn;
10357                  p = PieceToNumber((ChessSquare)p);
10358              if(p >= gameInfo.holdingsSize) p = 0;
10359              if(--board[p][BOARD_WIDTH-2] <= 0)
10360                   board[p][BOARD_WIDTH-1] = EmptySquare;
10361              if((int)board[p][BOARD_WIDTH-2] < 0)
10362                         board[p][BOARD_WIDTH-2] = 0;
10363         } else {                  /* black drop */
10364              p -= (int)BlackPawn;
10365                  p = PieceToNumber((ChessSquare)p);
10366              if(p >= gameInfo.holdingsSize) p = 0;
10367              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10368                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10369              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10370                         board[BOARD_HEIGHT-1-p][1] = 0;
10371         }
10372       }
10373       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10374           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10375         /* [HGM] holdings: Add to holdings, if holdings exist */
10376         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10377                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10378                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10379         }
10380         p = (int) captured;
10381         if (p >= (int) BlackPawn) {
10382           p -= (int)BlackPawn;
10383           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10384                   /* Restore shogi-promoted piece to its original  first */
10385                   captured = (ChessSquare) (DEMOTED(captured));
10386                   p = DEMOTED(p);
10387           }
10388           p = PieceToNumber((ChessSquare)p);
10389           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10390           board[p][BOARD_WIDTH-2]++;
10391           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10392         } else {
10393           p -= (int)WhitePawn;
10394           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10395                   captured = (ChessSquare) (DEMOTED(captured));
10396                   p = DEMOTED(p);
10397           }
10398           p = PieceToNumber((ChessSquare)p);
10399           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10400           board[BOARD_HEIGHT-1-p][1]++;
10401           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10402         }
10403       }
10404     } else if (gameInfo.variant == VariantAtomic) {
10405       if (captured != EmptySquare) {
10406         int y, x;
10407         for (y = toY-1; y <= toY+1; y++) {
10408           for (x = toX-1; x <= toX+1; x++) {
10409             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10410                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10411               board[y][x] = EmptySquare;
10412             }
10413           }
10414         }
10415         board[toY][toX] = EmptySquare;
10416       }
10417     }
10418
10419     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10420         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10421     } else
10422     if(promoChar == '+') {
10423         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10424         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10425         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10426           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10427     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10428         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10429         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10430            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10431         board[toY][toX] = newPiece;
10432     }
10433     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10434                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10435         // [HGM] superchess: take promotion piece out of holdings
10436         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10437         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10438             if(!--board[k][BOARD_WIDTH-2])
10439                 board[k][BOARD_WIDTH-1] = EmptySquare;
10440         } else {
10441             if(!--board[BOARD_HEIGHT-1-k][1])
10442                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10443         }
10444     }
10445 }
10446
10447 /* Updates forwardMostMove */
10448 void
10449 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10450 {
10451     int x = toX, y = toY;
10452     char *s = parseList[forwardMostMove];
10453     ChessSquare p = boards[forwardMostMove][toY][toX];
10454 //    forwardMostMove++; // [HGM] bare: moved downstream
10455
10456     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10457     (void) CoordsToAlgebraic(boards[forwardMostMove],
10458                              PosFlags(forwardMostMove),
10459                              fromY, fromX, y, x, promoChar,
10460                              s);
10461     if(killX >= 0 && killY >= 0)
10462         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10463
10464     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10465         int timeLeft; static int lastLoadFlag=0; int king, piece;
10466         piece = boards[forwardMostMove][fromY][fromX];
10467         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10468         if(gameInfo.variant == VariantKnightmate)
10469             king += (int) WhiteUnicorn - (int) WhiteKing;
10470         if(forwardMostMove == 0) {
10471             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10472                 fprintf(serverMoves, "%s;", UserName());
10473             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10474                 fprintf(serverMoves, "%s;", second.tidy);
10475             fprintf(serverMoves, "%s;", first.tidy);
10476             if(gameMode == MachinePlaysWhite)
10477                 fprintf(serverMoves, "%s;", UserName());
10478             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10479                 fprintf(serverMoves, "%s;", second.tidy);
10480         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10481         lastLoadFlag = loadFlag;
10482         // print base move
10483         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10484         // print castling suffix
10485         if( toY == fromY && piece == king ) {
10486             if(toX-fromX > 1)
10487                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10488             if(fromX-toX >1)
10489                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10490         }
10491         // e.p. suffix
10492         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10493              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10494              boards[forwardMostMove][toY][toX] == EmptySquare
10495              && fromX != toX && fromY != toY)
10496                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10497         // promotion suffix
10498         if(promoChar != NULLCHAR) {
10499             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10500                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10501                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10502             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10503         }
10504         if(!loadFlag) {
10505                 char buf[MOVE_LEN*2], *p; int len;
10506             fprintf(serverMoves, "/%d/%d",
10507                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10508             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10509             else                      timeLeft = blackTimeRemaining/1000;
10510             fprintf(serverMoves, "/%d", timeLeft);
10511                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10512                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10513                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10514                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10515             fprintf(serverMoves, "/%s", buf);
10516         }
10517         fflush(serverMoves);
10518     }
10519
10520     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10521         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10522       return;
10523     }
10524     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10525     if (commentList[forwardMostMove+1] != NULL) {
10526         free(commentList[forwardMostMove+1]);
10527         commentList[forwardMostMove+1] = NULL;
10528     }
10529     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10530     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10531     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10532     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10533     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10534     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10535     adjustedClock = FALSE;
10536     gameInfo.result = GameUnfinished;
10537     if (gameInfo.resultDetails != NULL) {
10538         free(gameInfo.resultDetails);
10539         gameInfo.resultDetails = NULL;
10540     }
10541     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10542                               moveList[forwardMostMove - 1]);
10543     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10544       case MT_NONE:
10545       case MT_STALEMATE:
10546       default:
10547         break;
10548       case MT_CHECK:
10549         if(!IS_SHOGI(gameInfo.variant))
10550             strcat(parseList[forwardMostMove - 1], "+");
10551         break;
10552       case MT_CHECKMATE:
10553       case MT_STAINMATE:
10554         strcat(parseList[forwardMostMove - 1], "#");
10555         break;
10556     }
10557 }
10558
10559 /* Updates currentMove if not pausing */
10560 void
10561 ShowMove (int fromX, int fromY, int toX, int toY)
10562 {
10563     int instant = (gameMode == PlayFromGameFile) ?
10564         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10565     if(appData.noGUI) return;
10566     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10567         if (!instant) {
10568             if (forwardMostMove == currentMove + 1) {
10569                 AnimateMove(boards[forwardMostMove - 1],
10570                             fromX, fromY, toX, toY);
10571             }
10572         }
10573         currentMove = forwardMostMove;
10574     }
10575
10576     killX = killY = -1; // [HGM] lion: used up
10577
10578     if (instant) return;
10579
10580     DisplayMove(currentMove - 1);
10581     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10582             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10583                 SetHighlights(fromX, fromY, toX, toY);
10584             }
10585     }
10586     DrawPosition(FALSE, boards[currentMove]);
10587     DisplayBothClocks();
10588     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10589 }
10590
10591 void
10592 SendEgtPath (ChessProgramState *cps)
10593 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10594         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10595
10596         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10597
10598         while(*p) {
10599             char c, *q = name+1, *r, *s;
10600
10601             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10602             while(*p && *p != ',') *q++ = *p++;
10603             *q++ = ':'; *q = 0;
10604             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10605                 strcmp(name, ",nalimov:") == 0 ) {
10606                 // take nalimov path from the menu-changeable option first, if it is defined
10607               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10608                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10609             } else
10610             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10611                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10612                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10613                 s = r = StrStr(s, ":") + 1; // beginning of path info
10614                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10615                 c = *r; *r = 0;             // temporarily null-terminate path info
10616                     *--q = 0;               // strip of trailig ':' from name
10617                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10618                 *r = c;
10619                 SendToProgram(buf,cps);     // send egtbpath command for this format
10620             }
10621             if(*p == ',') p++; // read away comma to position for next format name
10622         }
10623 }
10624
10625 static int
10626 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10627 {
10628       int width = 8, height = 8, holdings = 0;             // most common sizes
10629       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10630       // correct the deviations default for each variant
10631       if( v == VariantXiangqi ) width = 9,  height = 10;
10632       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10633       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10634       if( v == VariantCapablanca || v == VariantCapaRandom ||
10635           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10636                                 width = 10;
10637       if( v == VariantCourier ) width = 12;
10638       if( v == VariantSuper )                            holdings = 8;
10639       if( v == VariantGreat )   width = 10,              holdings = 8;
10640       if( v == VariantSChess )                           holdings = 7;
10641       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10642       if( v == VariantChuChess) width = 10, height = 10;
10643       if( v == VariantChu )     width = 12, height = 12;
10644       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10645              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10646              holdingsSize >= 0 && holdingsSize != holdings;
10647 }
10648
10649 char variantError[MSG_SIZ];
10650
10651 char *
10652 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10653 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10654       char *p, *variant = VariantName(v);
10655       static char b[MSG_SIZ];
10656       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10657            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10658                                                holdingsSize, variant); // cook up sized variant name
10659            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10660            if(StrStr(list, b) == NULL) {
10661                // specific sized variant not known, check if general sizing allowed
10662                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10663                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10664                             boardWidth, boardHeight, holdingsSize, engine);
10665                    return NULL;
10666                }
10667                /* [HGM] here we really should compare with the maximum supported board size */
10668            }
10669       } else snprintf(b, MSG_SIZ,"%s", variant);
10670       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10671       p = StrStr(list, b);
10672       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10673       if(p == NULL) {
10674           // occurs not at all in list, or only as sub-string
10675           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10676           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10677               int l = strlen(variantError);
10678               char *q;
10679               while(p != list && p[-1] != ',') p--;
10680               q = strchr(p, ',');
10681               if(q) *q = NULLCHAR;
10682               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10683               if(q) *q= ',';
10684           }
10685           return NULL;
10686       }
10687       return b;
10688 }
10689
10690 void
10691 InitChessProgram (ChessProgramState *cps, int setup)
10692 /* setup needed to setup FRC opening position */
10693 {
10694     char buf[MSG_SIZ], *b;
10695     if (appData.noChessProgram) return;
10696     hintRequested = FALSE;
10697     bookRequested = FALSE;
10698
10699     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10700     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10701     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10702     if(cps->memSize) { /* [HGM] memory */
10703       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10704         SendToProgram(buf, cps);
10705     }
10706     SendEgtPath(cps); /* [HGM] EGT */
10707     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10708       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10709         SendToProgram(buf, cps);
10710     }
10711
10712     setboardSpoiledMachineBlack = FALSE;
10713     SendToProgram(cps->initString, cps);
10714     if (gameInfo.variant != VariantNormal &&
10715         gameInfo.variant != VariantLoadable
10716         /* [HGM] also send variant if board size non-standard */
10717         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10718
10719       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10720                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10721       if (b == NULL) {
10722         VariantClass v;
10723         char c, *q = cps->variants, *p = strchr(q, ',');
10724         if(p) *p = NULLCHAR;
10725         v = StringToVariant(q);
10726         DisplayError(variantError, 0);
10727         if(v != VariantUnknown && cps == &first) {
10728             int w, h, s;
10729             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10730                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10731             ASSIGN(appData.variant, q);
10732             Reset(TRUE, FALSE);
10733         }
10734         if(p) *p = ',';
10735         return;
10736       }
10737
10738       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10739       SendToProgram(buf, cps);
10740     }
10741     currentlyInitializedVariant = gameInfo.variant;
10742
10743     /* [HGM] send opening position in FRC to first engine */
10744     if(setup) {
10745           SendToProgram("force\n", cps);
10746           SendBoard(cps, 0);
10747           /* engine is now in force mode! Set flag to wake it up after first move. */
10748           setboardSpoiledMachineBlack = 1;
10749     }
10750
10751     if (cps->sendICS) {
10752       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10753       SendToProgram(buf, cps);
10754     }
10755     cps->maybeThinking = FALSE;
10756     cps->offeredDraw = 0;
10757     if (!appData.icsActive) {
10758         SendTimeControl(cps, movesPerSession, timeControl,
10759                         timeIncrement, appData.searchDepth,
10760                         searchTime);
10761     }
10762     if (appData.showThinking
10763         // [HGM] thinking: four options require thinking output to be sent
10764         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10765                                 ) {
10766         SendToProgram("post\n", cps);
10767     }
10768     SendToProgram("hard\n", cps);
10769     if (!appData.ponderNextMove) {
10770         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10771            it without being sure what state we are in first.  "hard"
10772            is not a toggle, so that one is OK.
10773          */
10774         SendToProgram("easy\n", cps);
10775     }
10776     if (cps->usePing) {
10777       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10778       SendToProgram(buf, cps);
10779     }
10780     cps->initDone = TRUE;
10781     ClearEngineOutputPane(cps == &second);
10782 }
10783
10784
10785 void
10786 ResendOptions (ChessProgramState *cps)
10787 { // send the stored value of the options
10788   int i;
10789   char buf[MSG_SIZ];
10790   Option *opt = cps->option;
10791   for(i=0; i<cps->nrOptions; i++, opt++) {
10792       switch(opt->type) {
10793         case Spin:
10794         case Slider:
10795         case CheckBox:
10796             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10797           break;
10798         case ComboBox:
10799           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10800           break;
10801         default:
10802             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10803           break;
10804         case Button:
10805         case SaveButton:
10806           continue;
10807       }
10808       SendToProgram(buf, cps);
10809   }
10810 }
10811
10812 void
10813 StartChessProgram (ChessProgramState *cps)
10814 {
10815     char buf[MSG_SIZ];
10816     int err;
10817
10818     if (appData.noChessProgram) return;
10819     cps->initDone = FALSE;
10820
10821     if (strcmp(cps->host, "localhost") == 0) {
10822         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10823     } else if (*appData.remoteShell == NULLCHAR) {
10824         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10825     } else {
10826         if (*appData.remoteUser == NULLCHAR) {
10827           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10828                     cps->program);
10829         } else {
10830           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10831                     cps->host, appData.remoteUser, cps->program);
10832         }
10833         err = StartChildProcess(buf, "", &cps->pr);
10834     }
10835
10836     if (err != 0) {
10837       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10838         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10839         if(cps != &first) return;
10840         appData.noChessProgram = TRUE;
10841         ThawUI();
10842         SetNCPMode();
10843 //      DisplayFatalError(buf, err, 1);
10844 //      cps->pr = NoProc;
10845 //      cps->isr = NULL;
10846         return;
10847     }
10848
10849     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10850     if (cps->protocolVersion > 1) {
10851       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10852       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10853         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10854         cps->comboCnt = 0;  //                and values of combo boxes
10855       }
10856       SendToProgram(buf, cps);
10857       if(cps->reload) ResendOptions(cps);
10858     } else {
10859       SendToProgram("xboard\n", cps);
10860     }
10861 }
10862
10863 void
10864 TwoMachinesEventIfReady P((void))
10865 {
10866   static int curMess = 0;
10867   if (first.lastPing != first.lastPong) {
10868     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10869     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10870     return;
10871   }
10872   if (second.lastPing != second.lastPong) {
10873     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10874     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10875     return;
10876   }
10877   DisplayMessage("", ""); curMess = 0;
10878   TwoMachinesEvent();
10879 }
10880
10881 char *
10882 MakeName (char *template)
10883 {
10884     time_t clock;
10885     struct tm *tm;
10886     static char buf[MSG_SIZ];
10887     char *p = buf;
10888     int i;
10889
10890     clock = time((time_t *)NULL);
10891     tm = localtime(&clock);
10892
10893     while(*p++ = *template++) if(p[-1] == '%') {
10894         switch(*template++) {
10895           case 0:   *p = 0; return buf;
10896           case 'Y': i = tm->tm_year+1900; break;
10897           case 'y': i = tm->tm_year-100; break;
10898           case 'M': i = tm->tm_mon+1; break;
10899           case 'd': i = tm->tm_mday; break;
10900           case 'h': i = tm->tm_hour; break;
10901           case 'm': i = tm->tm_min; break;
10902           case 's': i = tm->tm_sec; break;
10903           default:  i = 0;
10904         }
10905         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10906     }
10907     return buf;
10908 }
10909
10910 int
10911 CountPlayers (char *p)
10912 {
10913     int n = 0;
10914     while(p = strchr(p, '\n')) p++, n++; // count participants
10915     return n;
10916 }
10917
10918 FILE *
10919 WriteTourneyFile (char *results, FILE *f)
10920 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10921     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10922     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10923         // create a file with tournament description
10924         fprintf(f, "-participants {%s}\n", appData.participants);
10925         fprintf(f, "-seedBase %d\n", appData.seedBase);
10926         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10927         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10928         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10929         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10930         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10931         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10932         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10933         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10934         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10935         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10936         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10937         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10938         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10939         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10940         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10941         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10942         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10943         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10944         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10945         fprintf(f, "-smpCores %d\n", appData.smpCores);
10946         if(searchTime > 0)
10947                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10948         else {
10949                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10950                 fprintf(f, "-tc %s\n", appData.timeControl);
10951                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10952         }
10953         fprintf(f, "-results \"%s\"\n", results);
10954     }
10955     return f;
10956 }
10957
10958 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10959
10960 void
10961 Substitute (char *participants, int expunge)
10962 {
10963     int i, changed, changes=0, nPlayers=0;
10964     char *p, *q, *r, buf[MSG_SIZ];
10965     if(participants == NULL) return;
10966     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10967     r = p = participants; q = appData.participants;
10968     while(*p && *p == *q) {
10969         if(*p == '\n') r = p+1, nPlayers++;
10970         p++; q++;
10971     }
10972     if(*p) { // difference
10973         while(*p && *p++ != '\n');
10974         while(*q && *q++ != '\n');
10975       changed = nPlayers;
10976         changes = 1 + (strcmp(p, q) != 0);
10977     }
10978     if(changes == 1) { // a single engine mnemonic was changed
10979         q = r; while(*q) nPlayers += (*q++ == '\n');
10980         p = buf; while(*r && (*p = *r++) != '\n') p++;
10981         *p = NULLCHAR;
10982         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10983         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10984         if(mnemonic[i]) { // The substitute is valid
10985             FILE *f;
10986             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10987                 flock(fileno(f), LOCK_EX);
10988                 ParseArgsFromFile(f);
10989                 fseek(f, 0, SEEK_SET);
10990                 FREE(appData.participants); appData.participants = participants;
10991                 if(expunge) { // erase results of replaced engine
10992                     int len = strlen(appData.results), w, b, dummy;
10993                     for(i=0; i<len; i++) {
10994                         Pairing(i, nPlayers, &w, &b, &dummy);
10995                         if((w == changed || b == changed) && appData.results[i] == '*') {
10996                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10997                             fclose(f);
10998                             return;
10999                         }
11000                     }
11001                     for(i=0; i<len; i++) {
11002                         Pairing(i, nPlayers, &w, &b, &dummy);
11003                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11004                     }
11005                 }
11006                 WriteTourneyFile(appData.results, f);
11007                 fclose(f); // release lock
11008                 return;
11009             }
11010         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11011     }
11012     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11013     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11014     free(participants);
11015     return;
11016 }
11017
11018 int
11019 CheckPlayers (char *participants)
11020 {
11021         int i;
11022         char buf[MSG_SIZ], *p;
11023         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11024         while(p = strchr(participants, '\n')) {
11025             *p = NULLCHAR;
11026             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11027             if(!mnemonic[i]) {
11028                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11029                 *p = '\n';
11030                 DisplayError(buf, 0);
11031                 return 1;
11032             }
11033             *p = '\n';
11034             participants = p + 1;
11035         }
11036         return 0;
11037 }
11038
11039 int
11040 CreateTourney (char *name)
11041 {
11042         FILE *f;
11043         if(matchMode && strcmp(name, appData.tourneyFile)) {
11044              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11045         }
11046         if(name[0] == NULLCHAR) {
11047             if(appData.participants[0])
11048                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11049             return 0;
11050         }
11051         f = fopen(name, "r");
11052         if(f) { // file exists
11053             ASSIGN(appData.tourneyFile, name);
11054             ParseArgsFromFile(f); // parse it
11055         } else {
11056             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11057             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11058                 DisplayError(_("Not enough participants"), 0);
11059                 return 0;
11060             }
11061             if(CheckPlayers(appData.participants)) return 0;
11062             ASSIGN(appData.tourneyFile, name);
11063             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11064             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11065         }
11066         fclose(f);
11067         appData.noChessProgram = FALSE;
11068         appData.clockMode = TRUE;
11069         SetGNUMode();
11070         return 1;
11071 }
11072
11073 int
11074 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11075 {
11076     char buf[MSG_SIZ], *p, *q;
11077     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11078     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11079     skip = !all && group[0]; // if group requested, we start in skip mode
11080     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11081         p = names; q = buf; header = 0;
11082         while(*p && *p != '\n') *q++ = *p++;
11083         *q = 0;
11084         if(*p == '\n') p++;
11085         if(buf[0] == '#') {
11086             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11087             depth++; // we must be entering a new group
11088             if(all) continue; // suppress printing group headers when complete list requested
11089             header = 1;
11090             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11091         }
11092         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11093         if(engineList[i]) free(engineList[i]);
11094         engineList[i] = strdup(buf);
11095         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11096         if(engineMnemonic[i]) free(engineMnemonic[i]);
11097         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11098             strcat(buf, " (");
11099             sscanf(q + 8, "%s", buf + strlen(buf));
11100             strcat(buf, ")");
11101         }
11102         engineMnemonic[i] = strdup(buf);
11103         i++;
11104     }
11105     engineList[i] = engineMnemonic[i] = NULL;
11106     return i;
11107 }
11108
11109 // following implemented as macro to avoid type limitations
11110 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11111
11112 void
11113 SwapEngines (int n)
11114 {   // swap settings for first engine and other engine (so far only some selected options)
11115     int h;
11116     char *p;
11117     if(n == 0) return;
11118     SWAP(directory, p)
11119     SWAP(chessProgram, p)
11120     SWAP(isUCI, h)
11121     SWAP(hasOwnBookUCI, h)
11122     SWAP(protocolVersion, h)
11123     SWAP(reuse, h)
11124     SWAP(scoreIsAbsolute, h)
11125     SWAP(timeOdds, h)
11126     SWAP(logo, p)
11127     SWAP(pgnName, p)
11128     SWAP(pvSAN, h)
11129     SWAP(engOptions, p)
11130     SWAP(engInitString, p)
11131     SWAP(computerString, p)
11132     SWAP(features, p)
11133     SWAP(fenOverride, p)
11134     SWAP(NPS, h)
11135     SWAP(accumulateTC, h)
11136     SWAP(drawDepth, h)
11137     SWAP(host, p)
11138     SWAP(pseudo, h)
11139 }
11140
11141 int
11142 GetEngineLine (char *s, int n)
11143 {
11144     int i;
11145     char buf[MSG_SIZ];
11146     extern char *icsNames;
11147     if(!s || !*s) return 0;
11148     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11149     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11150     if(!mnemonic[i]) return 0;
11151     if(n == 11) return 1; // just testing if there was a match
11152     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11153     if(n == 1) SwapEngines(n);
11154     ParseArgsFromString(buf);
11155     if(n == 1) SwapEngines(n);
11156     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11157         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11158         ParseArgsFromString(buf);
11159     }
11160     return 1;
11161 }
11162
11163 int
11164 SetPlayer (int player, char *p)
11165 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11166     int i;
11167     char buf[MSG_SIZ], *engineName;
11168     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11169     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11170     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11171     if(mnemonic[i]) {
11172         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11173         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11174         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11175         ParseArgsFromString(buf);
11176     } else { // no engine with this nickname is installed!
11177         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11178         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11179         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11180         ModeHighlight();
11181         DisplayError(buf, 0);
11182         return 0;
11183     }
11184     free(engineName);
11185     return i;
11186 }
11187
11188 char *recentEngines;
11189
11190 void
11191 RecentEngineEvent (int nr)
11192 {
11193     int n;
11194 //    SwapEngines(1); // bump first to second
11195 //    ReplaceEngine(&second, 1); // and load it there
11196     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11197     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11198     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11199         ReplaceEngine(&first, 0);
11200         FloatToFront(&appData.recentEngineList, command[n]);
11201     }
11202 }
11203
11204 int
11205 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11206 {   // determine players from game number
11207     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11208
11209     if(appData.tourneyType == 0) {
11210         roundsPerCycle = (nPlayers - 1) | 1;
11211         pairingsPerRound = nPlayers / 2;
11212     } else if(appData.tourneyType > 0) {
11213         roundsPerCycle = nPlayers - appData.tourneyType;
11214         pairingsPerRound = appData.tourneyType;
11215     }
11216     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11217     gamesPerCycle = gamesPerRound * roundsPerCycle;
11218     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11219     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11220     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11221     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11222     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11223     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11224
11225     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11226     if(appData.roundSync) *syncInterval = gamesPerRound;
11227
11228     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11229
11230     if(appData.tourneyType == 0) {
11231         if(curPairing == (nPlayers-1)/2 ) {
11232             *whitePlayer = curRound;
11233             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11234         } else {
11235             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11236             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11237             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11238             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11239         }
11240     } else if(appData.tourneyType > 1) {
11241         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11242         *whitePlayer = curRound + appData.tourneyType;
11243     } else if(appData.tourneyType > 0) {
11244         *whitePlayer = curPairing;
11245         *blackPlayer = curRound + appData.tourneyType;
11246     }
11247
11248     // take care of white/black alternation per round.
11249     // For cycles and games this is already taken care of by default, derived from matchGame!
11250     return curRound & 1;
11251 }
11252
11253 int
11254 NextTourneyGame (int nr, int *swapColors)
11255 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11256     char *p, *q;
11257     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11258     FILE *tf;
11259     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11260     tf = fopen(appData.tourneyFile, "r");
11261     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11262     ParseArgsFromFile(tf); fclose(tf);
11263     InitTimeControls(); // TC might be altered from tourney file
11264
11265     nPlayers = CountPlayers(appData.participants); // count participants
11266     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11267     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11268
11269     if(syncInterval) {
11270         p = q = appData.results;
11271         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11272         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11273             DisplayMessage(_("Waiting for other game(s)"),"");
11274             waitingForGame = TRUE;
11275             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11276             return 0;
11277         }
11278         waitingForGame = FALSE;
11279     }
11280
11281     if(appData.tourneyType < 0) {
11282         if(nr>=0 && !pairingReceived) {
11283             char buf[1<<16];
11284             if(pairing.pr == NoProc) {
11285                 if(!appData.pairingEngine[0]) {
11286                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11287                     return 0;
11288                 }
11289                 StartChessProgram(&pairing); // starts the pairing engine
11290             }
11291             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11292             SendToProgram(buf, &pairing);
11293             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11294             SendToProgram(buf, &pairing);
11295             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11296         }
11297         pairingReceived = 0;                              // ... so we continue here
11298         *swapColors = 0;
11299         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11300         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11301         matchGame = 1; roundNr = nr / syncInterval + 1;
11302     }
11303
11304     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11305
11306     // redefine engines, engine dir, etc.
11307     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11308     if(first.pr == NoProc) {
11309       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11310       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11311     }
11312     if(second.pr == NoProc) {
11313       SwapEngines(1);
11314       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11315       SwapEngines(1);         // and make that valid for second engine by swapping
11316       InitEngine(&second, 1);
11317     }
11318     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11319     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11320     return OK;
11321 }
11322
11323 void
11324 NextMatchGame ()
11325 {   // performs game initialization that does not invoke engines, and then tries to start the game
11326     int res, firstWhite, swapColors = 0;
11327     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11328     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
11329         char buf[MSG_SIZ];
11330         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11331         if(strcmp(buf, currentDebugFile)) { // name has changed
11332             FILE *f = fopen(buf, "w");
11333             if(f) { // if opening the new file failed, just keep using the old one
11334                 ASSIGN(currentDebugFile, buf);
11335                 fclose(debugFP);
11336                 debugFP = f;
11337             }
11338             if(appData.serverFileName) {
11339                 if(serverFP) fclose(serverFP);
11340                 serverFP = fopen(appData.serverFileName, "w");
11341                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11342                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11343             }
11344         }
11345     }
11346     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11347     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11348     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11349     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11350     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11351     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11352     Reset(FALSE, first.pr != NoProc);
11353     res = LoadGameOrPosition(matchGame); // setup game
11354     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11355     if(!res) return; // abort when bad game/pos file
11356     TwoMachinesEvent();
11357 }
11358
11359 void
11360 UserAdjudicationEvent (int result)
11361 {
11362     ChessMove gameResult = GameIsDrawn;
11363
11364     if( result > 0 ) {
11365         gameResult = WhiteWins;
11366     }
11367     else if( result < 0 ) {
11368         gameResult = BlackWins;
11369     }
11370
11371     if( gameMode == TwoMachinesPlay ) {
11372         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11373     }
11374 }
11375
11376
11377 // [HGM] save: calculate checksum of game to make games easily identifiable
11378 int
11379 StringCheckSum (char *s)
11380 {
11381         int i = 0;
11382         if(s==NULL) return 0;
11383         while(*s) i = i*259 + *s++;
11384         return i;
11385 }
11386
11387 int
11388 GameCheckSum ()
11389 {
11390         int i, sum=0;
11391         for(i=backwardMostMove; i<forwardMostMove; i++) {
11392                 sum += pvInfoList[i].depth;
11393                 sum += StringCheckSum(parseList[i]);
11394                 sum += StringCheckSum(commentList[i]);
11395                 sum *= 261;
11396         }
11397         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11398         return sum + StringCheckSum(commentList[i]);
11399 } // end of save patch
11400
11401 void
11402 GameEnds (ChessMove result, char *resultDetails, int whosays)
11403 {
11404     GameMode nextGameMode;
11405     int isIcsGame;
11406     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11407
11408     if(endingGame) return; /* [HGM] crash: forbid recursion */
11409     endingGame = 1;
11410     if(twoBoards) { // [HGM] dual: switch back to one board
11411         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11412         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11413     }
11414     if (appData.debugMode) {
11415       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11416               result, resultDetails ? resultDetails : "(null)", whosays);
11417     }
11418
11419     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11420
11421     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11422
11423     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11424         /* If we are playing on ICS, the server decides when the
11425            game is over, but the engine can offer to draw, claim
11426            a draw, or resign.
11427          */
11428 #if ZIPPY
11429         if (appData.zippyPlay && first.initDone) {
11430             if (result == GameIsDrawn) {
11431                 /* In case draw still needs to be claimed */
11432                 SendToICS(ics_prefix);
11433                 SendToICS("draw\n");
11434             } else if (StrCaseStr(resultDetails, "resign")) {
11435                 SendToICS(ics_prefix);
11436                 SendToICS("resign\n");
11437             }
11438         }
11439 #endif
11440         endingGame = 0; /* [HGM] crash */
11441         return;
11442     }
11443
11444     /* If we're loading the game from a file, stop */
11445     if (whosays == GE_FILE) {
11446       (void) StopLoadGameTimer();
11447       gameFileFP = NULL;
11448     }
11449
11450     /* Cancel draw offers */
11451     first.offeredDraw = second.offeredDraw = 0;
11452
11453     /* If this is an ICS game, only ICS can really say it's done;
11454        if not, anyone can. */
11455     isIcsGame = (gameMode == IcsPlayingWhite ||
11456                  gameMode == IcsPlayingBlack ||
11457                  gameMode == IcsObserving    ||
11458                  gameMode == IcsExamining);
11459
11460     if (!isIcsGame || whosays == GE_ICS) {
11461         /* OK -- not an ICS game, or ICS said it was done */
11462         StopClocks();
11463         if (!isIcsGame && !appData.noChessProgram)
11464           SetUserThinkingEnables();
11465
11466         /* [HGM] if a machine claims the game end we verify this claim */
11467         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11468             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11469                 char claimer;
11470                 ChessMove trueResult = (ChessMove) -1;
11471
11472                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11473                                             first.twoMachinesColor[0] :
11474                                             second.twoMachinesColor[0] ;
11475
11476                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11477                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11478                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11479                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11480                 } else
11481                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11482                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11483                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11484                 } else
11485                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11486                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11487                 }
11488
11489                 // now verify win claims, but not in drop games, as we don't understand those yet
11490                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11491                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11492                     (result == WhiteWins && claimer == 'w' ||
11493                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11494                       if (appData.debugMode) {
11495                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11496                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11497                       }
11498                       if(result != trueResult) {
11499                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11500                               result = claimer == 'w' ? BlackWins : WhiteWins;
11501                               resultDetails = buf;
11502                       }
11503                 } else
11504                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11505                     && (forwardMostMove <= backwardMostMove ||
11506                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11507                         (claimer=='b')==(forwardMostMove&1))
11508                                                                                   ) {
11509                       /* [HGM] verify: draws that were not flagged are false claims */
11510                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11511                       result = claimer == 'w' ? BlackWins : WhiteWins;
11512                       resultDetails = buf;
11513                 }
11514                 /* (Claiming a loss is accepted no questions asked!) */
11515             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11516                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11517                 result = GameUnfinished;
11518                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11519             }
11520             /* [HGM] bare: don't allow bare King to win */
11521             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11522                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11523                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11524                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11525                && result != GameIsDrawn)
11526             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11527                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11528                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11529                         if(p >= 0 && p <= (int)WhiteKing) k++;
11530                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11531                 }
11532                 if (appData.debugMode) {
11533                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11534                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11535                 }
11536                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11537                         result = GameIsDrawn;
11538                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11539                         resultDetails = buf;
11540                 }
11541             }
11542         }
11543
11544
11545         if(serverMoves != NULL && !loadFlag) { char c = '=';
11546             if(result==WhiteWins) c = '+';
11547             if(result==BlackWins) c = '-';
11548             if(resultDetails != NULL)
11549                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11550         }
11551         if (resultDetails != NULL) {
11552             gameInfo.result = result;
11553             gameInfo.resultDetails = StrSave(resultDetails);
11554
11555             /* display last move only if game was not loaded from file */
11556             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11557                 DisplayMove(currentMove - 1);
11558
11559             if (forwardMostMove != 0) {
11560                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11561                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11562                                                                 ) {
11563                     if (*appData.saveGameFile != NULLCHAR) {
11564                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11565                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11566                         else
11567                         SaveGameToFile(appData.saveGameFile, TRUE);
11568                     } else if (appData.autoSaveGames) {
11569                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11570                     }
11571                     if (*appData.savePositionFile != NULLCHAR) {
11572                         SavePositionToFile(appData.savePositionFile);
11573                     }
11574                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11575                 }
11576             }
11577
11578             /* Tell program how game ended in case it is learning */
11579             /* [HGM] Moved this to after saving the PGN, just in case */
11580             /* engine died and we got here through time loss. In that */
11581             /* case we will get a fatal error writing the pipe, which */
11582             /* would otherwise lose us the PGN.                       */
11583             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11584             /* output during GameEnds should never be fatal anymore   */
11585             if (gameMode == MachinePlaysWhite ||
11586                 gameMode == MachinePlaysBlack ||
11587                 gameMode == TwoMachinesPlay ||
11588                 gameMode == IcsPlayingWhite ||
11589                 gameMode == IcsPlayingBlack ||
11590                 gameMode == BeginningOfGame) {
11591                 char buf[MSG_SIZ];
11592                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11593                         resultDetails);
11594                 if (first.pr != NoProc) {
11595                     SendToProgram(buf, &first);
11596                 }
11597                 if (second.pr != NoProc &&
11598                     gameMode == TwoMachinesPlay) {
11599                     SendToProgram(buf, &second);
11600                 }
11601             }
11602         }
11603
11604         if (appData.icsActive) {
11605             if (appData.quietPlay &&
11606                 (gameMode == IcsPlayingWhite ||
11607                  gameMode == IcsPlayingBlack)) {
11608                 SendToICS(ics_prefix);
11609                 SendToICS("set shout 1\n");
11610             }
11611             nextGameMode = IcsIdle;
11612             ics_user_moved = FALSE;
11613             /* clean up premove.  It's ugly when the game has ended and the
11614              * premove highlights are still on the board.
11615              */
11616             if (gotPremove) {
11617               gotPremove = FALSE;
11618               ClearPremoveHighlights();
11619               DrawPosition(FALSE, boards[currentMove]);
11620             }
11621             if (whosays == GE_ICS) {
11622                 switch (result) {
11623                 case WhiteWins:
11624                     if (gameMode == IcsPlayingWhite)
11625                         PlayIcsWinSound();
11626                     else if(gameMode == IcsPlayingBlack)
11627                         PlayIcsLossSound();
11628                     break;
11629                 case BlackWins:
11630                     if (gameMode == IcsPlayingBlack)
11631                         PlayIcsWinSound();
11632                     else if(gameMode == IcsPlayingWhite)
11633                         PlayIcsLossSound();
11634                     break;
11635                 case GameIsDrawn:
11636                     PlayIcsDrawSound();
11637                     break;
11638                 default:
11639                     PlayIcsUnfinishedSound();
11640                 }
11641             }
11642             if(appData.quitNext) { ExitEvent(0); return; }
11643         } else if (gameMode == EditGame ||
11644                    gameMode == PlayFromGameFile ||
11645                    gameMode == AnalyzeMode ||
11646                    gameMode == AnalyzeFile) {
11647             nextGameMode = gameMode;
11648         } else {
11649             nextGameMode = EndOfGame;
11650         }
11651         pausing = FALSE;
11652         ModeHighlight();
11653     } else {
11654         nextGameMode = gameMode;
11655     }
11656
11657     if (appData.noChessProgram) {
11658         gameMode = nextGameMode;
11659         ModeHighlight();
11660         endingGame = 0; /* [HGM] crash */
11661         return;
11662     }
11663
11664     if (first.reuse) {
11665         /* Put first chess program into idle state */
11666         if (first.pr != NoProc &&
11667             (gameMode == MachinePlaysWhite ||
11668              gameMode == MachinePlaysBlack ||
11669              gameMode == TwoMachinesPlay ||
11670              gameMode == IcsPlayingWhite ||
11671              gameMode == IcsPlayingBlack ||
11672              gameMode == BeginningOfGame)) {
11673             SendToProgram("force\n", &first);
11674             if (first.usePing) {
11675               char buf[MSG_SIZ];
11676               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11677               SendToProgram(buf, &first);
11678             }
11679         }
11680     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11681         /* Kill off first chess program */
11682         if (first.isr != NULL)
11683           RemoveInputSource(first.isr);
11684         first.isr = NULL;
11685
11686         if (first.pr != NoProc) {
11687             ExitAnalyzeMode();
11688             DoSleep( appData.delayBeforeQuit );
11689             SendToProgram("quit\n", &first);
11690             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11691             first.reload = TRUE;
11692         }
11693         first.pr = NoProc;
11694     }
11695     if (second.reuse) {
11696         /* Put second chess program into idle state */
11697         if (second.pr != NoProc &&
11698             gameMode == TwoMachinesPlay) {
11699             SendToProgram("force\n", &second);
11700             if (second.usePing) {
11701               char buf[MSG_SIZ];
11702               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11703               SendToProgram(buf, &second);
11704             }
11705         }
11706     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11707         /* Kill off second chess program */
11708         if (second.isr != NULL)
11709           RemoveInputSource(second.isr);
11710         second.isr = NULL;
11711
11712         if (second.pr != NoProc) {
11713             DoSleep( appData.delayBeforeQuit );
11714             SendToProgram("quit\n", &second);
11715             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11716             second.reload = TRUE;
11717         }
11718         second.pr = NoProc;
11719     }
11720
11721     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11722         char resChar = '=';
11723         switch (result) {
11724         case WhiteWins:
11725           resChar = '+';
11726           if (first.twoMachinesColor[0] == 'w') {
11727             first.matchWins++;
11728           } else {
11729             second.matchWins++;
11730           }
11731           break;
11732         case BlackWins:
11733           resChar = '-';
11734           if (first.twoMachinesColor[0] == 'b') {
11735             first.matchWins++;
11736           } else {
11737             second.matchWins++;
11738           }
11739           break;
11740         case GameUnfinished:
11741           resChar = ' ';
11742         default:
11743           break;
11744         }
11745
11746         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11747         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11748             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11749             ReserveGame(nextGame, resChar); // sets nextGame
11750             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11751             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11752         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11753
11754         if (nextGame <= appData.matchGames && !abortMatch) {
11755             gameMode = nextGameMode;
11756             matchGame = nextGame; // this will be overruled in tourney mode!
11757             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11758             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11759             endingGame = 0; /* [HGM] crash */
11760             return;
11761         } else {
11762             gameMode = nextGameMode;
11763             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11764                      first.tidy, second.tidy,
11765                      first.matchWins, second.matchWins,
11766                      appData.matchGames - (first.matchWins + second.matchWins));
11767             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11768             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11769             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11770             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11771                 first.twoMachinesColor = "black\n";
11772                 second.twoMachinesColor = "white\n";
11773             } else {
11774                 first.twoMachinesColor = "white\n";
11775                 second.twoMachinesColor = "black\n";
11776             }
11777         }
11778     }
11779     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11780         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11781       ExitAnalyzeMode();
11782     gameMode = nextGameMode;
11783     ModeHighlight();
11784     endingGame = 0;  /* [HGM] crash */
11785     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11786         if(matchMode == TRUE) { // match through command line: exit with or without popup
11787             if(ranking) {
11788                 ToNrEvent(forwardMostMove);
11789                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11790                 else ExitEvent(0);
11791             } else DisplayFatalError(buf, 0, 0);
11792         } else { // match through menu; just stop, with or without popup
11793             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11794             ModeHighlight();
11795             if(ranking){
11796                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11797             } else DisplayNote(buf);
11798       }
11799       if(ranking) free(ranking);
11800     }
11801 }
11802
11803 /* Assumes program was just initialized (initString sent).
11804    Leaves program in force mode. */
11805 void
11806 FeedMovesToProgram (ChessProgramState *cps, int upto)
11807 {
11808     int i;
11809
11810     if (appData.debugMode)
11811       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11812               startedFromSetupPosition ? "position and " : "",
11813               backwardMostMove, upto, cps->which);
11814     if(currentlyInitializedVariant != gameInfo.variant) {
11815       char buf[MSG_SIZ];
11816         // [HGM] variantswitch: make engine aware of new variant
11817         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11818                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11819                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11820         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11821         SendToProgram(buf, cps);
11822         currentlyInitializedVariant = gameInfo.variant;
11823     }
11824     SendToProgram("force\n", cps);
11825     if (startedFromSetupPosition) {
11826         SendBoard(cps, backwardMostMove);
11827     if (appData.debugMode) {
11828         fprintf(debugFP, "feedMoves\n");
11829     }
11830     }
11831     for (i = backwardMostMove; i < upto; i++) {
11832         SendMoveToProgram(i, cps);
11833     }
11834 }
11835
11836
11837 int
11838 ResurrectChessProgram ()
11839 {
11840      /* The chess program may have exited.
11841         If so, restart it and feed it all the moves made so far. */
11842     static int doInit = 0;
11843
11844     if (appData.noChessProgram) return 1;
11845
11846     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11847         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11848         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11849         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11850     } else {
11851         if (first.pr != NoProc) return 1;
11852         StartChessProgram(&first);
11853     }
11854     InitChessProgram(&first, FALSE);
11855     FeedMovesToProgram(&first, currentMove);
11856
11857     if (!first.sendTime) {
11858         /* can't tell gnuchess what its clock should read,
11859            so we bow to its notion. */
11860         ResetClocks();
11861         timeRemaining[0][currentMove] = whiteTimeRemaining;
11862         timeRemaining[1][currentMove] = blackTimeRemaining;
11863     }
11864
11865     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11866                 appData.icsEngineAnalyze) && first.analysisSupport) {
11867       SendToProgram("analyze\n", &first);
11868       first.analyzing = TRUE;
11869     }
11870     return 1;
11871 }
11872
11873 /*
11874  * Button procedures
11875  */
11876 void
11877 Reset (int redraw, int init)
11878 {
11879     int i;
11880
11881     if (appData.debugMode) {
11882         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11883                 redraw, init, gameMode);
11884     }
11885     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11886     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11887     CleanupTail(); // [HGM] vari: delete any stored variations
11888     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11889     pausing = pauseExamInvalid = FALSE;
11890     startedFromSetupPosition = blackPlaysFirst = FALSE;
11891     firstMove = TRUE;
11892     whiteFlag = blackFlag = FALSE;
11893     userOfferedDraw = FALSE;
11894     hintRequested = bookRequested = FALSE;
11895     first.maybeThinking = FALSE;
11896     second.maybeThinking = FALSE;
11897     first.bookSuspend = FALSE; // [HGM] book
11898     second.bookSuspend = FALSE;
11899     thinkOutput[0] = NULLCHAR;
11900     lastHint[0] = NULLCHAR;
11901     ClearGameInfo(&gameInfo);
11902     gameInfo.variant = StringToVariant(appData.variant);
11903     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11904     ics_user_moved = ics_clock_paused = FALSE;
11905     ics_getting_history = H_FALSE;
11906     ics_gamenum = -1;
11907     white_holding[0] = black_holding[0] = NULLCHAR;
11908     ClearProgramStats();
11909     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11910
11911     ResetFrontEnd();
11912     ClearHighlights();
11913     flipView = appData.flipView;
11914     ClearPremoveHighlights();
11915     gotPremove = FALSE;
11916     alarmSounded = FALSE;
11917     killX = killY = -1; // [HGM] lion
11918
11919     GameEnds(EndOfFile, NULL, GE_PLAYER);
11920     if(appData.serverMovesName != NULL) {
11921         /* [HGM] prepare to make moves file for broadcasting */
11922         clock_t t = clock();
11923         if(serverMoves != NULL) fclose(serverMoves);
11924         serverMoves = fopen(appData.serverMovesName, "r");
11925         if(serverMoves != NULL) {
11926             fclose(serverMoves);
11927             /* delay 15 sec before overwriting, so all clients can see end */
11928             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11929         }
11930         serverMoves = fopen(appData.serverMovesName, "w");
11931     }
11932
11933     ExitAnalyzeMode();
11934     gameMode = BeginningOfGame;
11935     ModeHighlight();
11936     if(appData.icsActive) gameInfo.variant = VariantNormal;
11937     currentMove = forwardMostMove = backwardMostMove = 0;
11938     MarkTargetSquares(1);
11939     InitPosition(redraw);
11940     for (i = 0; i < MAX_MOVES; i++) {
11941         if (commentList[i] != NULL) {
11942             free(commentList[i]);
11943             commentList[i] = NULL;
11944         }
11945     }
11946     ResetClocks();
11947     timeRemaining[0][0] = whiteTimeRemaining;
11948     timeRemaining[1][0] = blackTimeRemaining;
11949
11950     if (first.pr == NoProc) {
11951         StartChessProgram(&first);
11952     }
11953     if (init) {
11954             InitChessProgram(&first, startedFromSetupPosition);
11955     }
11956     DisplayTitle("");
11957     DisplayMessage("", "");
11958     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11959     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11960     ClearMap();        // [HGM] exclude: invalidate map
11961 }
11962
11963 void
11964 AutoPlayGameLoop ()
11965 {
11966     for (;;) {
11967         if (!AutoPlayOneMove())
11968           return;
11969         if (matchMode || appData.timeDelay == 0)
11970           continue;
11971         if (appData.timeDelay < 0)
11972           return;
11973         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11974         break;
11975     }
11976 }
11977
11978 void
11979 AnalyzeNextGame()
11980 {
11981     ReloadGame(1); // next game
11982 }
11983
11984 int
11985 AutoPlayOneMove ()
11986 {
11987     int fromX, fromY, toX, toY;
11988
11989     if (appData.debugMode) {
11990       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11991     }
11992
11993     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11994       return FALSE;
11995
11996     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11997       pvInfoList[currentMove].depth = programStats.depth;
11998       pvInfoList[currentMove].score = programStats.score;
11999       pvInfoList[currentMove].time  = 0;
12000       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12001       else { // append analysis of final position as comment
12002         char buf[MSG_SIZ];
12003         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12004         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12005       }
12006       programStats.depth = 0;
12007     }
12008
12009     if (currentMove >= forwardMostMove) {
12010       if(gameMode == AnalyzeFile) {
12011           if(appData.loadGameIndex == -1) {
12012             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12013           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12014           } else {
12015           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12016         }
12017       }
12018 //      gameMode = EndOfGame;
12019 //      ModeHighlight();
12020
12021       /* [AS] Clear current move marker at the end of a game */
12022       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12023
12024       return FALSE;
12025     }
12026
12027     toX = moveList[currentMove][2] - AAA;
12028     toY = moveList[currentMove][3] - ONE;
12029
12030     if (moveList[currentMove][1] == '@') {
12031         if (appData.highlightLastMove) {
12032             SetHighlights(-1, -1, toX, toY);
12033         }
12034     } else {
12035         int viaX = moveList[currentMove][5] - AAA;
12036         int viaY = moveList[currentMove][6] - ONE;
12037         fromX = moveList[currentMove][0] - AAA;
12038         fromY = moveList[currentMove][1] - ONE;
12039
12040         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12041
12042         if(moveList[currentMove][4] == ';') { // multi-leg
12043             ChessSquare piece = boards[currentMove][viaY][viaX];
12044             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
12045             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
12046             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
12047             boards[currentMove][viaY][viaX] = piece;
12048         } else
12049         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12050
12051         if (appData.highlightLastMove) {
12052             SetHighlights(fromX, fromY, toX, toY);
12053         }
12054     }
12055     DisplayMove(currentMove);
12056     SendMoveToProgram(currentMove++, &first);
12057     DisplayBothClocks();
12058     DrawPosition(FALSE, boards[currentMove]);
12059     // [HGM] PV info: always display, routine tests if empty
12060     DisplayComment(currentMove - 1, commentList[currentMove]);
12061     return TRUE;
12062 }
12063
12064
12065 int
12066 LoadGameOneMove (ChessMove readAhead)
12067 {
12068     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12069     char promoChar = NULLCHAR;
12070     ChessMove moveType;
12071     char move[MSG_SIZ];
12072     char *p, *q;
12073
12074     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12075         gameMode != AnalyzeMode && gameMode != Training) {
12076         gameFileFP = NULL;
12077         return FALSE;
12078     }
12079
12080     yyboardindex = forwardMostMove;
12081     if (readAhead != EndOfFile) {
12082       moveType = readAhead;
12083     } else {
12084       if (gameFileFP == NULL)
12085           return FALSE;
12086       moveType = (ChessMove) Myylex();
12087     }
12088
12089     done = FALSE;
12090     switch (moveType) {
12091       case Comment:
12092         if (appData.debugMode)
12093           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12094         p = yy_text;
12095
12096         /* append the comment but don't display it */
12097         AppendComment(currentMove, p, FALSE);
12098         return TRUE;
12099
12100       case WhiteCapturesEnPassant:
12101       case BlackCapturesEnPassant:
12102       case WhitePromotion:
12103       case BlackPromotion:
12104       case WhiteNonPromotion:
12105       case BlackNonPromotion:
12106       case NormalMove:
12107       case FirstLeg:
12108       case WhiteKingSideCastle:
12109       case WhiteQueenSideCastle:
12110       case BlackKingSideCastle:
12111       case BlackQueenSideCastle:
12112       case WhiteKingSideCastleWild:
12113       case WhiteQueenSideCastleWild:
12114       case BlackKingSideCastleWild:
12115       case BlackQueenSideCastleWild:
12116       /* PUSH Fabien */
12117       case WhiteHSideCastleFR:
12118       case WhiteASideCastleFR:
12119       case BlackHSideCastleFR:
12120       case BlackASideCastleFR:
12121       /* POP Fabien */
12122         if (appData.debugMode)
12123           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12124         fromX = currentMoveString[0] - AAA;
12125         fromY = currentMoveString[1] - ONE;
12126         toX = currentMoveString[2] - AAA;
12127         toY = currentMoveString[3] - ONE;
12128         promoChar = currentMoveString[4];
12129         if(promoChar == ';') promoChar = NULLCHAR;
12130         break;
12131
12132       case WhiteDrop:
12133       case BlackDrop:
12134         if (appData.debugMode)
12135           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12136         fromX = moveType == WhiteDrop ?
12137           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12138         (int) CharToPiece(ToLower(currentMoveString[0]));
12139         fromY = DROP_RANK;
12140         toX = currentMoveString[2] - AAA;
12141         toY = currentMoveString[3] - ONE;
12142         break;
12143
12144       case WhiteWins:
12145       case BlackWins:
12146       case GameIsDrawn:
12147       case GameUnfinished:
12148         if (appData.debugMode)
12149           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12150         p = strchr(yy_text, '{');
12151         if (p == NULL) p = strchr(yy_text, '(');
12152         if (p == NULL) {
12153             p = yy_text;
12154             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12155         } else {
12156             q = strchr(p, *p == '{' ? '}' : ')');
12157             if (q != NULL) *q = NULLCHAR;
12158             p++;
12159         }
12160         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12161         GameEnds(moveType, p, GE_FILE);
12162         done = TRUE;
12163         if (cmailMsgLoaded) {
12164             ClearHighlights();
12165             flipView = WhiteOnMove(currentMove);
12166             if (moveType == GameUnfinished) flipView = !flipView;
12167             if (appData.debugMode)
12168               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12169         }
12170         break;
12171
12172       case EndOfFile:
12173         if (appData.debugMode)
12174           fprintf(debugFP, "Parser hit end of file\n");
12175         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12176           case MT_NONE:
12177           case MT_CHECK:
12178             break;
12179           case MT_CHECKMATE:
12180           case MT_STAINMATE:
12181             if (WhiteOnMove(currentMove)) {
12182                 GameEnds(BlackWins, "Black mates", GE_FILE);
12183             } else {
12184                 GameEnds(WhiteWins, "White mates", GE_FILE);
12185             }
12186             break;
12187           case MT_STALEMATE:
12188             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12189             break;
12190         }
12191         done = TRUE;
12192         break;
12193
12194       case MoveNumberOne:
12195         if (lastLoadGameStart == GNUChessGame) {
12196             /* GNUChessGames have numbers, but they aren't move numbers */
12197             if (appData.debugMode)
12198               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12199                       yy_text, (int) moveType);
12200             return LoadGameOneMove(EndOfFile); /* tail recursion */
12201         }
12202         /* else fall thru */
12203
12204       case XBoardGame:
12205       case GNUChessGame:
12206       case PGNTag:
12207         /* Reached start of next game in file */
12208         if (appData.debugMode)
12209           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12210         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12211           case MT_NONE:
12212           case MT_CHECK:
12213             break;
12214           case MT_CHECKMATE:
12215           case MT_STAINMATE:
12216             if (WhiteOnMove(currentMove)) {
12217                 GameEnds(BlackWins, "Black mates", GE_FILE);
12218             } else {
12219                 GameEnds(WhiteWins, "White mates", GE_FILE);
12220             }
12221             break;
12222           case MT_STALEMATE:
12223             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12224             break;
12225         }
12226         done = TRUE;
12227         break;
12228
12229       case PositionDiagram:     /* should not happen; ignore */
12230       case ElapsedTime:         /* ignore */
12231       case NAG:                 /* ignore */
12232         if (appData.debugMode)
12233           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12234                   yy_text, (int) moveType);
12235         return LoadGameOneMove(EndOfFile); /* tail recursion */
12236
12237       case IllegalMove:
12238         if (appData.testLegality) {
12239             if (appData.debugMode)
12240               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12241             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12242                     (forwardMostMove / 2) + 1,
12243                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12244             DisplayError(move, 0);
12245             done = TRUE;
12246         } else {
12247             if (appData.debugMode)
12248               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12249                       yy_text, currentMoveString);
12250             if(currentMoveString[1] == '@') {
12251                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12252                 fromY = DROP_RANK;
12253             } else {
12254                 fromX = currentMoveString[0] - AAA;
12255                 fromY = currentMoveString[1] - ONE;
12256             }
12257             toX = currentMoveString[2] - AAA;
12258             toY = currentMoveString[3] - ONE;
12259             promoChar = currentMoveString[4];
12260         }
12261         break;
12262
12263       case AmbiguousMove:
12264         if (appData.debugMode)
12265           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12266         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12267                 (forwardMostMove / 2) + 1,
12268                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12269         DisplayError(move, 0);
12270         done = TRUE;
12271         break;
12272
12273       default:
12274       case ImpossibleMove:
12275         if (appData.debugMode)
12276           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12277         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12278                 (forwardMostMove / 2) + 1,
12279                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12280         DisplayError(move, 0);
12281         done = TRUE;
12282         break;
12283     }
12284
12285     if (done) {
12286         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12287             DrawPosition(FALSE, boards[currentMove]);
12288             DisplayBothClocks();
12289             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12290               DisplayComment(currentMove - 1, commentList[currentMove]);
12291         }
12292         (void) StopLoadGameTimer();
12293         gameFileFP = NULL;
12294         cmailOldMove = forwardMostMove;
12295         return FALSE;
12296     } else {
12297         /* currentMoveString is set as a side-effect of yylex */
12298
12299         thinkOutput[0] = NULLCHAR;
12300         MakeMove(fromX, fromY, toX, toY, promoChar);
12301         killX = killY = -1; // [HGM] lion: used up
12302         currentMove = forwardMostMove;
12303         return TRUE;
12304     }
12305 }
12306
12307 /* Load the nth game from the given file */
12308 int
12309 LoadGameFromFile (char *filename, int n, char *title, int useList)
12310 {
12311     FILE *f;
12312     char buf[MSG_SIZ];
12313
12314     if (strcmp(filename, "-") == 0) {
12315         f = stdin;
12316         title = "stdin";
12317     } else {
12318         f = fopen(filename, "rb");
12319         if (f == NULL) {
12320           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12321             DisplayError(buf, errno);
12322             return FALSE;
12323         }
12324     }
12325     if (fseek(f, 0, 0) == -1) {
12326         /* f is not seekable; probably a pipe */
12327         useList = FALSE;
12328     }
12329     if (useList && n == 0) {
12330         int error = GameListBuild(f);
12331         if (error) {
12332             DisplayError(_("Cannot build game list"), error);
12333         } else if (!ListEmpty(&gameList) &&
12334                    ((ListGame *) gameList.tailPred)->number > 1) {
12335             GameListPopUp(f, title);
12336             return TRUE;
12337         }
12338         GameListDestroy();
12339         n = 1;
12340     }
12341     if (n == 0) n = 1;
12342     return LoadGame(f, n, title, FALSE);
12343 }
12344
12345
12346 void
12347 MakeRegisteredMove ()
12348 {
12349     int fromX, fromY, toX, toY;
12350     char promoChar;
12351     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12352         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12353           case CMAIL_MOVE:
12354           case CMAIL_DRAW:
12355             if (appData.debugMode)
12356               fprintf(debugFP, "Restoring %s for game %d\n",
12357                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12358
12359             thinkOutput[0] = NULLCHAR;
12360             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12361             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12362             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12363             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12364             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12365             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12366             MakeMove(fromX, fromY, toX, toY, promoChar);
12367             ShowMove(fromX, fromY, toX, toY);
12368
12369             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12370               case MT_NONE:
12371               case MT_CHECK:
12372                 break;
12373
12374               case MT_CHECKMATE:
12375               case MT_STAINMATE:
12376                 if (WhiteOnMove(currentMove)) {
12377                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12378                 } else {
12379                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12380                 }
12381                 break;
12382
12383               case MT_STALEMATE:
12384                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12385                 break;
12386             }
12387
12388             break;
12389
12390           case CMAIL_RESIGN:
12391             if (WhiteOnMove(currentMove)) {
12392                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12393             } else {
12394                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12395             }
12396             break;
12397
12398           case CMAIL_ACCEPT:
12399             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12400             break;
12401
12402           default:
12403             break;
12404         }
12405     }
12406
12407     return;
12408 }
12409
12410 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12411 int
12412 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12413 {
12414     int retVal;
12415
12416     if (gameNumber > nCmailGames) {
12417         DisplayError(_("No more games in this message"), 0);
12418         return FALSE;
12419     }
12420     if (f == lastLoadGameFP) {
12421         int offset = gameNumber - lastLoadGameNumber;
12422         if (offset == 0) {
12423             cmailMsg[0] = NULLCHAR;
12424             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12425                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12426                 nCmailMovesRegistered--;
12427             }
12428             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12429             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12430                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12431             }
12432         } else {
12433             if (! RegisterMove()) return FALSE;
12434         }
12435     }
12436
12437     retVal = LoadGame(f, gameNumber, title, useList);
12438
12439     /* Make move registered during previous look at this game, if any */
12440     MakeRegisteredMove();
12441
12442     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12443         commentList[currentMove]
12444           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12445         DisplayComment(currentMove - 1, commentList[currentMove]);
12446     }
12447
12448     return retVal;
12449 }
12450
12451 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12452 int
12453 ReloadGame (int offset)
12454 {
12455     int gameNumber = lastLoadGameNumber + offset;
12456     if (lastLoadGameFP == NULL) {
12457         DisplayError(_("No game has been loaded yet"), 0);
12458         return FALSE;
12459     }
12460     if (gameNumber <= 0) {
12461         DisplayError(_("Can't back up any further"), 0);
12462         return FALSE;
12463     }
12464     if (cmailMsgLoaded) {
12465         return CmailLoadGame(lastLoadGameFP, gameNumber,
12466                              lastLoadGameTitle, lastLoadGameUseList);
12467     } else {
12468         return LoadGame(lastLoadGameFP, gameNumber,
12469                         lastLoadGameTitle, lastLoadGameUseList);
12470     }
12471 }
12472
12473 int keys[EmptySquare+1];
12474
12475 int
12476 PositionMatches (Board b1, Board b2)
12477 {
12478     int r, f, sum=0;
12479     switch(appData.searchMode) {
12480         case 1: return CompareWithRights(b1, b2);
12481         case 2:
12482             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12483                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12484             }
12485             return TRUE;
12486         case 3:
12487             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12488               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12489                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12490             }
12491             return sum==0;
12492         case 4:
12493             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12494                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12495             }
12496             return sum==0;
12497     }
12498     return TRUE;
12499 }
12500
12501 #define Q_PROMO  4
12502 #define Q_EP     3
12503 #define Q_BCASTL 2
12504 #define Q_WCASTL 1
12505
12506 int pieceList[256], quickBoard[256];
12507 ChessSquare pieceType[256] = { EmptySquare };
12508 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12509 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12510 int soughtTotal, turn;
12511 Boolean epOK, flipSearch;
12512
12513 typedef struct {
12514     unsigned char piece, to;
12515 } Move;
12516
12517 #define DSIZE (250000)
12518
12519 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12520 Move *moveDatabase = initialSpace;
12521 unsigned int movePtr, dataSize = DSIZE;
12522
12523 int
12524 MakePieceList (Board board, int *counts)
12525 {
12526     int r, f, n=Q_PROMO, total=0;
12527     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12528     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12529         int sq = f + (r<<4);
12530         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12531             quickBoard[sq] = ++n;
12532             pieceList[n] = sq;
12533             pieceType[n] = board[r][f];
12534             counts[board[r][f]]++;
12535             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12536             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12537             total++;
12538         }
12539     }
12540     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12541     return total;
12542 }
12543
12544 void
12545 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12546 {
12547     int sq = fromX + (fromY<<4);
12548     int piece = quickBoard[sq], rook;
12549     quickBoard[sq] = 0;
12550     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12551     if(piece == pieceList[1] && fromY == toY) {
12552       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12553         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12554         moveDatabase[movePtr++].piece = Q_WCASTL;
12555         quickBoard[sq] = piece;
12556         piece = quickBoard[from]; quickBoard[from] = 0;
12557         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12558       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12559         quickBoard[sq] = 0; // remove Rook
12560         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12561         moveDatabase[movePtr++].piece = Q_WCASTL;
12562         quickBoard[sq] = pieceList[1]; // put King
12563         piece = rook;
12564         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12565       }
12566     } else
12567     if(piece == pieceList[2] && fromY == toY) {
12568       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12569         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12570         moveDatabase[movePtr++].piece = Q_BCASTL;
12571         quickBoard[sq] = piece;
12572         piece = quickBoard[from]; quickBoard[from] = 0;
12573         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12574       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12575         quickBoard[sq] = 0; // remove Rook
12576         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12577         moveDatabase[movePtr++].piece = Q_BCASTL;
12578         quickBoard[sq] = pieceList[2]; // put King
12579         piece = rook;
12580         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12581       }
12582     } else
12583     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12584         quickBoard[(fromY<<4)+toX] = 0;
12585         moveDatabase[movePtr].piece = Q_EP;
12586         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12587         moveDatabase[movePtr].to = sq;
12588     } else
12589     if(promoPiece != pieceType[piece]) {
12590         moveDatabase[movePtr++].piece = Q_PROMO;
12591         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12592     }
12593     moveDatabase[movePtr].piece = piece;
12594     quickBoard[sq] = piece;
12595     movePtr++;
12596 }
12597
12598 int
12599 PackGame (Board board)
12600 {
12601     Move *newSpace = NULL;
12602     moveDatabase[movePtr].piece = 0; // terminate previous game
12603     if(movePtr > dataSize) {
12604         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12605         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12606         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12607         if(newSpace) {
12608             int i;
12609             Move *p = moveDatabase, *q = newSpace;
12610             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12611             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12612             moveDatabase = newSpace;
12613         } else { // calloc failed, we must be out of memory. Too bad...
12614             dataSize = 0; // prevent calloc events for all subsequent games
12615             return 0;     // and signal this one isn't cached
12616         }
12617     }
12618     movePtr++;
12619     MakePieceList(board, counts);
12620     return movePtr;
12621 }
12622
12623 int
12624 QuickCompare (Board board, int *minCounts, int *maxCounts)
12625 {   // compare according to search mode
12626     int r, f;
12627     switch(appData.searchMode)
12628     {
12629       case 1: // exact position match
12630         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12631         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12632             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12633         }
12634         break;
12635       case 2: // can have extra material on empty squares
12636         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12637             if(board[r][f] == EmptySquare) continue;
12638             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12639         }
12640         break;
12641       case 3: // material with exact Pawn structure
12642         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12643             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12644             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12645         } // fall through to material comparison
12646       case 4: // exact material
12647         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12648         break;
12649       case 6: // material range with given imbalance
12650         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12651         // fall through to range comparison
12652       case 5: // material range
12653         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12654     }
12655     return TRUE;
12656 }
12657
12658 int
12659 QuickScan (Board board, Move *move)
12660 {   // reconstruct game,and compare all positions in it
12661     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12662     do {
12663         int piece = move->piece;
12664         int to = move->to, from = pieceList[piece];
12665         if(found < 0) { // if already found just scan to game end for final piece count
12666           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12667            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12668            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12669                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12670             ) {
12671             static int lastCounts[EmptySquare+1];
12672             int i;
12673             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12674             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12675           } else stretch = 0;
12676           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12677           if(found >= 0 && !appData.minPieces) return found;
12678         }
12679         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12680           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12681           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12682             piece = (++move)->piece;
12683             from = pieceList[piece];
12684             counts[pieceType[piece]]--;
12685             pieceType[piece] = (ChessSquare) move->to;
12686             counts[move->to]++;
12687           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12688             counts[pieceType[quickBoard[to]]]--;
12689             quickBoard[to] = 0; total--;
12690             move++;
12691             continue;
12692           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12693             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12694             from  = pieceList[piece]; // so this must be King
12695             quickBoard[from] = 0;
12696             pieceList[piece] = to;
12697             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12698             quickBoard[from] = 0; // rook
12699             quickBoard[to] = piece;
12700             to = move->to; piece = move->piece;
12701             goto aftercastle;
12702           }
12703         }
12704         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12705         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12706         quickBoard[from] = 0;
12707       aftercastle:
12708         quickBoard[to] = piece;
12709         pieceList[piece] = to;
12710         cnt++; turn ^= 3;
12711         move++;
12712     } while(1);
12713 }
12714
12715 void
12716 InitSearch ()
12717 {
12718     int r, f;
12719     flipSearch = FALSE;
12720     CopyBoard(soughtBoard, boards[currentMove]);
12721     soughtTotal = MakePieceList(soughtBoard, maxSought);
12722     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12723     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12724     CopyBoard(reverseBoard, boards[currentMove]);
12725     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12726         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12727         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12728         reverseBoard[r][f] = piece;
12729     }
12730     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12731     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12732     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12733                  || (boards[currentMove][CASTLING][2] == NoRights ||
12734                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12735                  && (boards[currentMove][CASTLING][5] == NoRights ||
12736                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12737       ) {
12738         flipSearch = TRUE;
12739         CopyBoard(flipBoard, soughtBoard);
12740         CopyBoard(rotateBoard, reverseBoard);
12741         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12742             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12743             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12744         }
12745     }
12746     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12747     if(appData.searchMode >= 5) {
12748         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12749         MakePieceList(soughtBoard, minSought);
12750         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12751     }
12752     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12753         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12754 }
12755
12756 GameInfo dummyInfo;
12757 static int creatingBook;
12758
12759 int
12760 GameContainsPosition (FILE *f, ListGame *lg)
12761 {
12762     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12763     int fromX, fromY, toX, toY;
12764     char promoChar;
12765     static int initDone=FALSE;
12766
12767     // weed out games based on numerical tag comparison
12768     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12769     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12770     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12771     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12772     if(!initDone) {
12773         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12774         initDone = TRUE;
12775     }
12776     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12777     else CopyBoard(boards[scratch], initialPosition); // default start position
12778     if(lg->moves) {
12779         turn = btm + 1;
12780         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12781         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12782     }
12783     if(btm) plyNr++;
12784     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12785     fseek(f, lg->offset, 0);
12786     yynewfile(f);
12787     while(1) {
12788         yyboardindex = scratch;
12789         quickFlag = plyNr+1;
12790         next = Myylex();
12791         quickFlag = 0;
12792         switch(next) {
12793             case PGNTag:
12794                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12795             default:
12796                 continue;
12797
12798             case XBoardGame:
12799             case GNUChessGame:
12800                 if(plyNr) return -1; // after we have seen moves, this is for new game
12801               continue;
12802
12803             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12804             case ImpossibleMove:
12805             case WhiteWins: // game ends here with these four
12806             case BlackWins:
12807             case GameIsDrawn:
12808             case GameUnfinished:
12809                 return -1;
12810
12811             case IllegalMove:
12812                 if(appData.testLegality) return -1;
12813             case WhiteCapturesEnPassant:
12814             case BlackCapturesEnPassant:
12815             case WhitePromotion:
12816             case BlackPromotion:
12817             case WhiteNonPromotion:
12818             case BlackNonPromotion:
12819             case NormalMove:
12820             case FirstLeg:
12821             case WhiteKingSideCastle:
12822             case WhiteQueenSideCastle:
12823             case BlackKingSideCastle:
12824             case BlackQueenSideCastle:
12825             case WhiteKingSideCastleWild:
12826             case WhiteQueenSideCastleWild:
12827             case BlackKingSideCastleWild:
12828             case BlackQueenSideCastleWild:
12829             case WhiteHSideCastleFR:
12830             case WhiteASideCastleFR:
12831             case BlackHSideCastleFR:
12832             case BlackASideCastleFR:
12833                 fromX = currentMoveString[0] - AAA;
12834                 fromY = currentMoveString[1] - ONE;
12835                 toX = currentMoveString[2] - AAA;
12836                 toY = currentMoveString[3] - ONE;
12837                 promoChar = currentMoveString[4];
12838                 break;
12839             case WhiteDrop:
12840             case BlackDrop:
12841                 fromX = next == WhiteDrop ?
12842                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12843                   (int) CharToPiece(ToLower(currentMoveString[0]));
12844                 fromY = DROP_RANK;
12845                 toX = currentMoveString[2] - AAA;
12846                 toY = currentMoveString[3] - ONE;
12847                 promoChar = 0;
12848                 break;
12849         }
12850         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12851         plyNr++;
12852         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12853         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12854         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12855         if(appData.findMirror) {
12856             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12857             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12858         }
12859     }
12860 }
12861
12862 /* Load the nth game from open file f */
12863 int
12864 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12865 {
12866     ChessMove cm;
12867     char buf[MSG_SIZ];
12868     int gn = gameNumber;
12869     ListGame *lg = NULL;
12870     int numPGNTags = 0;
12871     int err, pos = -1;
12872     GameMode oldGameMode;
12873     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12874     char oldName[MSG_SIZ];
12875
12876     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12877
12878     if (appData.debugMode)
12879         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12880
12881     if (gameMode == Training )
12882         SetTrainingModeOff();
12883
12884     oldGameMode = gameMode;
12885     if (gameMode != BeginningOfGame) {
12886       Reset(FALSE, TRUE);
12887     }
12888     killX = killY = -1; // [HGM] lion: in case we did not Reset
12889
12890     gameFileFP = f;
12891     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12892         fclose(lastLoadGameFP);
12893     }
12894
12895     if (useList) {
12896         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12897
12898         if (lg) {
12899             fseek(f, lg->offset, 0);
12900             GameListHighlight(gameNumber);
12901             pos = lg->position;
12902             gn = 1;
12903         }
12904         else {
12905             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12906               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12907             else
12908             DisplayError(_("Game number out of range"), 0);
12909             return FALSE;
12910         }
12911     } else {
12912         GameListDestroy();
12913         if (fseek(f, 0, 0) == -1) {
12914             if (f == lastLoadGameFP ?
12915                 gameNumber == lastLoadGameNumber + 1 :
12916                 gameNumber == 1) {
12917                 gn = 1;
12918             } else {
12919                 DisplayError(_("Can't seek on game file"), 0);
12920                 return FALSE;
12921             }
12922         }
12923     }
12924     lastLoadGameFP = f;
12925     lastLoadGameNumber = gameNumber;
12926     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12927     lastLoadGameUseList = useList;
12928
12929     yynewfile(f);
12930
12931     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12932       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12933                 lg->gameInfo.black);
12934             DisplayTitle(buf);
12935     } else if (*title != NULLCHAR) {
12936         if (gameNumber > 1) {
12937           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12938             DisplayTitle(buf);
12939         } else {
12940             DisplayTitle(title);
12941         }
12942     }
12943
12944     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12945         gameMode = PlayFromGameFile;
12946         ModeHighlight();
12947     }
12948
12949     currentMove = forwardMostMove = backwardMostMove = 0;
12950     CopyBoard(boards[0], initialPosition);
12951     StopClocks();
12952
12953     /*
12954      * Skip the first gn-1 games in the file.
12955      * Also skip over anything that precedes an identifiable
12956      * start of game marker, to avoid being confused by
12957      * garbage at the start of the file.  Currently
12958      * recognized start of game markers are the move number "1",
12959      * the pattern "gnuchess .* game", the pattern
12960      * "^[#;%] [^ ]* game file", and a PGN tag block.
12961      * A game that starts with one of the latter two patterns
12962      * will also have a move number 1, possibly
12963      * following a position diagram.
12964      * 5-4-02: Let's try being more lenient and allowing a game to
12965      * start with an unnumbered move.  Does that break anything?
12966      */
12967     cm = lastLoadGameStart = EndOfFile;
12968     while (gn > 0) {
12969         yyboardindex = forwardMostMove;
12970         cm = (ChessMove) Myylex();
12971         switch (cm) {
12972           case EndOfFile:
12973             if (cmailMsgLoaded) {
12974                 nCmailGames = CMAIL_MAX_GAMES - gn;
12975             } else {
12976                 Reset(TRUE, TRUE);
12977                 DisplayError(_("Game not found in file"), 0);
12978             }
12979             return FALSE;
12980
12981           case GNUChessGame:
12982           case XBoardGame:
12983             gn--;
12984             lastLoadGameStart = cm;
12985             break;
12986
12987           case MoveNumberOne:
12988             switch (lastLoadGameStart) {
12989               case GNUChessGame:
12990               case XBoardGame:
12991               case PGNTag:
12992                 break;
12993               case MoveNumberOne:
12994               case EndOfFile:
12995                 gn--;           /* count this game */
12996                 lastLoadGameStart = cm;
12997                 break;
12998               default:
12999                 /* impossible */
13000                 break;
13001             }
13002             break;
13003
13004           case PGNTag:
13005             switch (lastLoadGameStart) {
13006               case GNUChessGame:
13007               case PGNTag:
13008               case MoveNumberOne:
13009               case EndOfFile:
13010                 gn--;           /* count this game */
13011                 lastLoadGameStart = cm;
13012                 break;
13013               case XBoardGame:
13014                 lastLoadGameStart = cm; /* game counted already */
13015                 break;
13016               default:
13017                 /* impossible */
13018                 break;
13019             }
13020             if (gn > 0) {
13021                 do {
13022                     yyboardindex = forwardMostMove;
13023                     cm = (ChessMove) Myylex();
13024                 } while (cm == PGNTag || cm == Comment);
13025             }
13026             break;
13027
13028           case WhiteWins:
13029           case BlackWins:
13030           case GameIsDrawn:
13031             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13032                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13033                     != CMAIL_OLD_RESULT) {
13034                     nCmailResults ++ ;
13035                     cmailResult[  CMAIL_MAX_GAMES
13036                                 - gn - 1] = CMAIL_OLD_RESULT;
13037                 }
13038             }
13039             break;
13040
13041           case NormalMove:
13042           case FirstLeg:
13043             /* Only a NormalMove can be at the start of a game
13044              * without a position diagram. */
13045             if (lastLoadGameStart == EndOfFile ) {
13046               gn--;
13047               lastLoadGameStart = MoveNumberOne;
13048             }
13049             break;
13050
13051           default:
13052             break;
13053         }
13054     }
13055
13056     if (appData.debugMode)
13057       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13058
13059     if (cm == XBoardGame) {
13060         /* Skip any header junk before position diagram and/or move 1 */
13061         for (;;) {
13062             yyboardindex = forwardMostMove;
13063             cm = (ChessMove) Myylex();
13064
13065             if (cm == EndOfFile ||
13066                 cm == GNUChessGame || cm == XBoardGame) {
13067                 /* Empty game; pretend end-of-file and handle later */
13068                 cm = EndOfFile;
13069                 break;
13070             }
13071
13072             if (cm == MoveNumberOne || cm == PositionDiagram ||
13073                 cm == PGNTag || cm == Comment)
13074               break;
13075         }
13076     } else if (cm == GNUChessGame) {
13077         if (gameInfo.event != NULL) {
13078             free(gameInfo.event);
13079         }
13080         gameInfo.event = StrSave(yy_text);
13081     }
13082
13083     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13084     while (cm == PGNTag) {
13085         if (appData.debugMode)
13086           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13087         err = ParsePGNTag(yy_text, &gameInfo);
13088         if (!err) numPGNTags++;
13089
13090         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13091         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13092             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13093             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13094             InitPosition(TRUE);
13095             oldVariant = gameInfo.variant;
13096             if (appData.debugMode)
13097               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13098         }
13099
13100
13101         if (gameInfo.fen != NULL) {
13102           Board initial_position;
13103           startedFromSetupPosition = TRUE;
13104           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13105             Reset(TRUE, TRUE);
13106             DisplayError(_("Bad FEN position in file"), 0);
13107             return FALSE;
13108           }
13109           CopyBoard(boards[0], initial_position);
13110           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13111             CopyBoard(initialPosition, initial_position);
13112           if (blackPlaysFirst) {
13113             currentMove = forwardMostMove = backwardMostMove = 1;
13114             CopyBoard(boards[1], initial_position);
13115             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13116             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13117             timeRemaining[0][1] = whiteTimeRemaining;
13118             timeRemaining[1][1] = blackTimeRemaining;
13119             if (commentList[0] != NULL) {
13120               commentList[1] = commentList[0];
13121               commentList[0] = NULL;
13122             }
13123           } else {
13124             currentMove = forwardMostMove = backwardMostMove = 0;
13125           }
13126           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13127           {   int i;
13128               initialRulePlies = FENrulePlies;
13129               for( i=0; i< nrCastlingRights; i++ )
13130                   initialRights[i] = initial_position[CASTLING][i];
13131           }
13132           yyboardindex = forwardMostMove;
13133           free(gameInfo.fen);
13134           gameInfo.fen = NULL;
13135         }
13136
13137         yyboardindex = forwardMostMove;
13138         cm = (ChessMove) Myylex();
13139
13140         /* Handle comments interspersed among the tags */
13141         while (cm == Comment) {
13142             char *p;
13143             if (appData.debugMode)
13144               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13145             p = yy_text;
13146             AppendComment(currentMove, p, FALSE);
13147             yyboardindex = forwardMostMove;
13148             cm = (ChessMove) Myylex();
13149         }
13150     }
13151
13152     /* don't rely on existence of Event tag since if game was
13153      * pasted from clipboard the Event tag may not exist
13154      */
13155     if (numPGNTags > 0){
13156         char *tags;
13157         if (gameInfo.variant == VariantNormal) {
13158           VariantClass v = StringToVariant(gameInfo.event);
13159           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13160           if(v < VariantShogi) gameInfo.variant = v;
13161         }
13162         if (!matchMode) {
13163           if( appData.autoDisplayTags ) {
13164             tags = PGNTags(&gameInfo);
13165             TagsPopUp(tags, CmailMsg());
13166             free(tags);
13167           }
13168         }
13169     } else {
13170         /* Make something up, but don't display it now */
13171         SetGameInfo();
13172         TagsPopDown();
13173     }
13174
13175     if (cm == PositionDiagram) {
13176         int i, j;
13177         char *p;
13178         Board initial_position;
13179
13180         if (appData.debugMode)
13181           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13182
13183         if (!startedFromSetupPosition) {
13184             p = yy_text;
13185             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13186               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13187                 switch (*p) {
13188                   case '{':
13189                   case '[':
13190                   case '-':
13191                   case ' ':
13192                   case '\t':
13193                   case '\n':
13194                   case '\r':
13195                     break;
13196                   default:
13197                     initial_position[i][j++] = CharToPiece(*p);
13198                     break;
13199                 }
13200             while (*p == ' ' || *p == '\t' ||
13201                    *p == '\n' || *p == '\r') p++;
13202
13203             if (strncmp(p, "black", strlen("black"))==0)
13204               blackPlaysFirst = TRUE;
13205             else
13206               blackPlaysFirst = FALSE;
13207             startedFromSetupPosition = TRUE;
13208
13209             CopyBoard(boards[0], initial_position);
13210             if (blackPlaysFirst) {
13211                 currentMove = forwardMostMove = backwardMostMove = 1;
13212                 CopyBoard(boards[1], initial_position);
13213                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13214                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13215                 timeRemaining[0][1] = whiteTimeRemaining;
13216                 timeRemaining[1][1] = blackTimeRemaining;
13217                 if (commentList[0] != NULL) {
13218                     commentList[1] = commentList[0];
13219                     commentList[0] = NULL;
13220                 }
13221             } else {
13222                 currentMove = forwardMostMove = backwardMostMove = 0;
13223             }
13224         }
13225         yyboardindex = forwardMostMove;
13226         cm = (ChessMove) Myylex();
13227     }
13228
13229   if(!creatingBook) {
13230     if (first.pr == NoProc) {
13231         StartChessProgram(&first);
13232     }
13233     InitChessProgram(&first, FALSE);
13234     if(gameInfo.variant == VariantUnknown && *oldName) {
13235         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13236         gameInfo.variant = v;
13237     }
13238     SendToProgram("force\n", &first);
13239     if (startedFromSetupPosition) {
13240         SendBoard(&first, forwardMostMove);
13241     if (appData.debugMode) {
13242         fprintf(debugFP, "Load Game\n");
13243     }
13244         DisplayBothClocks();
13245     }
13246   }
13247
13248     /* [HGM] server: flag to write setup moves in broadcast file as one */
13249     loadFlag = appData.suppressLoadMoves;
13250
13251     while (cm == Comment) {
13252         char *p;
13253         if (appData.debugMode)
13254           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13255         p = yy_text;
13256         AppendComment(currentMove, p, FALSE);
13257         yyboardindex = forwardMostMove;
13258         cm = (ChessMove) Myylex();
13259     }
13260
13261     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13262         cm == WhiteWins || cm == BlackWins ||
13263         cm == GameIsDrawn || cm == GameUnfinished) {
13264         DisplayMessage("", _("No moves in game"));
13265         if (cmailMsgLoaded) {
13266             if (appData.debugMode)
13267               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13268             ClearHighlights();
13269             flipView = FALSE;
13270         }
13271         DrawPosition(FALSE, boards[currentMove]);
13272         DisplayBothClocks();
13273         gameMode = EditGame;
13274         ModeHighlight();
13275         gameFileFP = NULL;
13276         cmailOldMove = 0;
13277         return TRUE;
13278     }
13279
13280     // [HGM] PV info: routine tests if comment empty
13281     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13282         DisplayComment(currentMove - 1, commentList[currentMove]);
13283     }
13284     if (!matchMode && appData.timeDelay != 0)
13285       DrawPosition(FALSE, boards[currentMove]);
13286
13287     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13288       programStats.ok_to_send = 1;
13289     }
13290
13291     /* if the first token after the PGN tags is a move
13292      * and not move number 1, retrieve it from the parser
13293      */
13294     if (cm != MoveNumberOne)
13295         LoadGameOneMove(cm);
13296
13297     /* load the remaining moves from the file */
13298     while (LoadGameOneMove(EndOfFile)) {
13299       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13300       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13301     }
13302
13303     /* rewind to the start of the game */
13304     currentMove = backwardMostMove;
13305
13306     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13307
13308     if (oldGameMode == AnalyzeFile) {
13309       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13310       AnalyzeFileEvent();
13311     } else
13312     if (oldGameMode == AnalyzeMode) {
13313       AnalyzeFileEvent();
13314     }
13315
13316     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13317         long int w, b; // [HGM] adjourn: restore saved clock times
13318         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13319         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13320             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13321             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13322         }
13323     }
13324
13325     if(creatingBook) return TRUE;
13326     if (!matchMode && pos > 0) {
13327         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13328     } else
13329     if (matchMode || appData.timeDelay == 0) {
13330       ToEndEvent();
13331     } else if (appData.timeDelay > 0) {
13332       AutoPlayGameLoop();
13333     }
13334
13335     if (appData.debugMode)
13336         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13337
13338     loadFlag = 0; /* [HGM] true game starts */
13339     return TRUE;
13340 }
13341
13342 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13343 int
13344 ReloadPosition (int offset)
13345 {
13346     int positionNumber = lastLoadPositionNumber + offset;
13347     if (lastLoadPositionFP == NULL) {
13348         DisplayError(_("No position has been loaded yet"), 0);
13349         return FALSE;
13350     }
13351     if (positionNumber <= 0) {
13352         DisplayError(_("Can't back up any further"), 0);
13353         return FALSE;
13354     }
13355     return LoadPosition(lastLoadPositionFP, positionNumber,
13356                         lastLoadPositionTitle);
13357 }
13358
13359 /* Load the nth position from the given file */
13360 int
13361 LoadPositionFromFile (char *filename, int n, char *title)
13362 {
13363     FILE *f;
13364     char buf[MSG_SIZ];
13365
13366     if (strcmp(filename, "-") == 0) {
13367         return LoadPosition(stdin, n, "stdin");
13368     } else {
13369         f = fopen(filename, "rb");
13370         if (f == NULL) {
13371             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13372             DisplayError(buf, errno);
13373             return FALSE;
13374         } else {
13375             return LoadPosition(f, n, title);
13376         }
13377     }
13378 }
13379
13380 /* Load the nth position from the given open file, and close it */
13381 int
13382 LoadPosition (FILE *f, int positionNumber, char *title)
13383 {
13384     char *p, line[MSG_SIZ];
13385     Board initial_position;
13386     int i, j, fenMode, pn;
13387
13388     if (gameMode == Training )
13389         SetTrainingModeOff();
13390
13391     if (gameMode != BeginningOfGame) {
13392         Reset(FALSE, TRUE);
13393     }
13394     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13395         fclose(lastLoadPositionFP);
13396     }
13397     if (positionNumber == 0) positionNumber = 1;
13398     lastLoadPositionFP = f;
13399     lastLoadPositionNumber = positionNumber;
13400     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13401     if (first.pr == NoProc && !appData.noChessProgram) {
13402       StartChessProgram(&first);
13403       InitChessProgram(&first, FALSE);
13404     }
13405     pn = positionNumber;
13406     if (positionNumber < 0) {
13407         /* Negative position number means to seek to that byte offset */
13408         if (fseek(f, -positionNumber, 0) == -1) {
13409             DisplayError(_("Can't seek on position file"), 0);
13410             return FALSE;
13411         };
13412         pn = 1;
13413     } else {
13414         if (fseek(f, 0, 0) == -1) {
13415             if (f == lastLoadPositionFP ?
13416                 positionNumber == lastLoadPositionNumber + 1 :
13417                 positionNumber == 1) {
13418                 pn = 1;
13419             } else {
13420                 DisplayError(_("Can't seek on position file"), 0);
13421                 return FALSE;
13422             }
13423         }
13424     }
13425     /* See if this file is FEN or old-style xboard */
13426     if (fgets(line, MSG_SIZ, f) == NULL) {
13427         DisplayError(_("Position not found in file"), 0);
13428         return FALSE;
13429     }
13430     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13431     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13432
13433     if (pn >= 2) {
13434         if (fenMode || line[0] == '#') pn--;
13435         while (pn > 0) {
13436             /* skip positions before number pn */
13437             if (fgets(line, MSG_SIZ, f) == NULL) {
13438                 Reset(TRUE, TRUE);
13439                 DisplayError(_("Position not found in file"), 0);
13440                 return FALSE;
13441             }
13442             if (fenMode || line[0] == '#') pn--;
13443         }
13444     }
13445
13446     if (fenMode) {
13447         char *p;
13448         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13449             DisplayError(_("Bad FEN position in file"), 0);
13450             return FALSE;
13451         }
13452         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13453             sscanf(p+3, "%s", bestMove);
13454         } else *bestMove = NULLCHAR;
13455     } else {
13456         (void) fgets(line, MSG_SIZ, f);
13457         (void) fgets(line, MSG_SIZ, f);
13458
13459         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13460             (void) fgets(line, MSG_SIZ, f);
13461             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13462                 if (*p == ' ')
13463                   continue;
13464                 initial_position[i][j++] = CharToPiece(*p);
13465             }
13466         }
13467
13468         blackPlaysFirst = FALSE;
13469         if (!feof(f)) {
13470             (void) fgets(line, MSG_SIZ, f);
13471             if (strncmp(line, "black", strlen("black"))==0)
13472               blackPlaysFirst = TRUE;
13473         }
13474     }
13475     startedFromSetupPosition = TRUE;
13476
13477     CopyBoard(boards[0], initial_position);
13478     if (blackPlaysFirst) {
13479         currentMove = forwardMostMove = backwardMostMove = 1;
13480         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13481         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13482         CopyBoard(boards[1], initial_position);
13483         DisplayMessage("", _("Black to play"));
13484     } else {
13485         currentMove = forwardMostMove = backwardMostMove = 0;
13486         DisplayMessage("", _("White to play"));
13487     }
13488     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13489     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13490         SendToProgram("force\n", &first);
13491         SendBoard(&first, forwardMostMove);
13492     }
13493     if (appData.debugMode) {
13494 int i, j;
13495   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13496   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13497         fprintf(debugFP, "Load Position\n");
13498     }
13499
13500     if (positionNumber > 1) {
13501       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13502         DisplayTitle(line);
13503     } else {
13504         DisplayTitle(title);
13505     }
13506     gameMode = EditGame;
13507     ModeHighlight();
13508     ResetClocks();
13509     timeRemaining[0][1] = whiteTimeRemaining;
13510     timeRemaining[1][1] = blackTimeRemaining;
13511     DrawPosition(FALSE, boards[currentMove]);
13512
13513     return TRUE;
13514 }
13515
13516
13517 void
13518 CopyPlayerNameIntoFileName (char **dest, char *src)
13519 {
13520     while (*src != NULLCHAR && *src != ',') {
13521         if (*src == ' ') {
13522             *(*dest)++ = '_';
13523             src++;
13524         } else {
13525             *(*dest)++ = *src++;
13526         }
13527     }
13528 }
13529
13530 char *
13531 DefaultFileName (char *ext)
13532 {
13533     static char def[MSG_SIZ];
13534     char *p;
13535
13536     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13537         p = def;
13538         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13539         *p++ = '-';
13540         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13541         *p++ = '.';
13542         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13543     } else {
13544         def[0] = NULLCHAR;
13545     }
13546     return def;
13547 }
13548
13549 /* Save the current game to the given file */
13550 int
13551 SaveGameToFile (char *filename, int append)
13552 {
13553     FILE *f;
13554     char buf[MSG_SIZ];
13555     int result, i, t,tot=0;
13556
13557     if (strcmp(filename, "-") == 0) {
13558         return SaveGame(stdout, 0, NULL);
13559     } else {
13560         for(i=0; i<10; i++) { // upto 10 tries
13561              f = fopen(filename, append ? "a" : "w");
13562              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13563              if(f || errno != 13) break;
13564              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13565              tot += t;
13566         }
13567         if (f == NULL) {
13568             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13569             DisplayError(buf, errno);
13570             return FALSE;
13571         } else {
13572             safeStrCpy(buf, lastMsg, MSG_SIZ);
13573             DisplayMessage(_("Waiting for access to save file"), "");
13574             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13575             DisplayMessage(_("Saving game"), "");
13576             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13577             result = SaveGame(f, 0, NULL);
13578             DisplayMessage(buf, "");
13579             return result;
13580         }
13581     }
13582 }
13583
13584 char *
13585 SavePart (char *str)
13586 {
13587     static char buf[MSG_SIZ];
13588     char *p;
13589
13590     p = strchr(str, ' ');
13591     if (p == NULL) return str;
13592     strncpy(buf, str, p - str);
13593     buf[p - str] = NULLCHAR;
13594     return buf;
13595 }
13596
13597 #define PGN_MAX_LINE 75
13598
13599 #define PGN_SIDE_WHITE  0
13600 #define PGN_SIDE_BLACK  1
13601
13602 static int
13603 FindFirstMoveOutOfBook (int side)
13604 {
13605     int result = -1;
13606
13607     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13608         int index = backwardMostMove;
13609         int has_book_hit = 0;
13610
13611         if( (index % 2) != side ) {
13612             index++;
13613         }
13614
13615         while( index < forwardMostMove ) {
13616             /* Check to see if engine is in book */
13617             int depth = pvInfoList[index].depth;
13618             int score = pvInfoList[index].score;
13619             int in_book = 0;
13620
13621             if( depth <= 2 ) {
13622                 in_book = 1;
13623             }
13624             else if( score == 0 && depth == 63 ) {
13625                 in_book = 1; /* Zappa */
13626             }
13627             else if( score == 2 && depth == 99 ) {
13628                 in_book = 1; /* Abrok */
13629             }
13630
13631             has_book_hit += in_book;
13632
13633             if( ! in_book ) {
13634                 result = index;
13635
13636                 break;
13637             }
13638
13639             index += 2;
13640         }
13641     }
13642
13643     return result;
13644 }
13645
13646 void
13647 GetOutOfBookInfo (char * buf)
13648 {
13649     int oob[2];
13650     int i;
13651     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13652
13653     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13654     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13655
13656     *buf = '\0';
13657
13658     if( oob[0] >= 0 || oob[1] >= 0 ) {
13659         for( i=0; i<2; i++ ) {
13660             int idx = oob[i];
13661
13662             if( idx >= 0 ) {
13663                 if( i > 0 && oob[0] >= 0 ) {
13664                     strcat( buf, "   " );
13665                 }
13666
13667                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13668                 sprintf( buf+strlen(buf), "%s%.2f",
13669                     pvInfoList[idx].score >= 0 ? "+" : "",
13670                     pvInfoList[idx].score / 100.0 );
13671             }
13672         }
13673     }
13674 }
13675
13676 /* Save game in PGN style */
13677 static void
13678 SaveGamePGN2 (FILE *f)
13679 {
13680     int i, offset, linelen, newblock;
13681 //    char *movetext;
13682     char numtext[32];
13683     int movelen, numlen, blank;
13684     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13685
13686     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13687
13688     PrintPGNTags(f, &gameInfo);
13689
13690     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13691
13692     if (backwardMostMove > 0 || startedFromSetupPosition) {
13693         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13694         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13695         fprintf(f, "\n{--------------\n");
13696         PrintPosition(f, backwardMostMove);
13697         fprintf(f, "--------------}\n");
13698         free(fen);
13699     }
13700     else {
13701         /* [AS] Out of book annotation */
13702         if( appData.saveOutOfBookInfo ) {
13703             char buf[64];
13704
13705             GetOutOfBookInfo( buf );
13706
13707             if( buf[0] != '\0' ) {
13708                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13709             }
13710         }
13711
13712         fprintf(f, "\n");
13713     }
13714
13715     i = backwardMostMove;
13716     linelen = 0;
13717     newblock = TRUE;
13718
13719     while (i < forwardMostMove) {
13720         /* Print comments preceding this move */
13721         if (commentList[i] != NULL) {
13722             if (linelen > 0) fprintf(f, "\n");
13723             fprintf(f, "%s", commentList[i]);
13724             linelen = 0;
13725             newblock = TRUE;
13726         }
13727
13728         /* Format move number */
13729         if ((i % 2) == 0)
13730           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13731         else
13732           if (newblock)
13733             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13734           else
13735             numtext[0] = NULLCHAR;
13736
13737         numlen = strlen(numtext);
13738         newblock = FALSE;
13739
13740         /* Print move number */
13741         blank = linelen > 0 && numlen > 0;
13742         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13743             fprintf(f, "\n");
13744             linelen = 0;
13745             blank = 0;
13746         }
13747         if (blank) {
13748             fprintf(f, " ");
13749             linelen++;
13750         }
13751         fprintf(f, "%s", numtext);
13752         linelen += numlen;
13753
13754         /* Get move */
13755         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13756         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13757
13758         /* Print move */
13759         blank = linelen > 0 && movelen > 0;
13760         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13761             fprintf(f, "\n");
13762             linelen = 0;
13763             blank = 0;
13764         }
13765         if (blank) {
13766             fprintf(f, " ");
13767             linelen++;
13768         }
13769         fprintf(f, "%s", move_buffer);
13770         linelen += movelen;
13771
13772         /* [AS] Add PV info if present */
13773         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13774             /* [HGM] add time */
13775             char buf[MSG_SIZ]; int seconds;
13776
13777             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13778
13779             if( seconds <= 0)
13780               buf[0] = 0;
13781             else
13782               if( seconds < 30 )
13783                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13784               else
13785                 {
13786                   seconds = (seconds + 4)/10; // round to full seconds
13787                   if( seconds < 60 )
13788                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13789                   else
13790                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13791                 }
13792
13793             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13794                       pvInfoList[i].score >= 0 ? "+" : "",
13795                       pvInfoList[i].score / 100.0,
13796                       pvInfoList[i].depth,
13797                       buf );
13798
13799             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13800
13801             /* Print score/depth */
13802             blank = linelen > 0 && movelen > 0;
13803             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13804                 fprintf(f, "\n");
13805                 linelen = 0;
13806                 blank = 0;
13807             }
13808             if (blank) {
13809                 fprintf(f, " ");
13810                 linelen++;
13811             }
13812             fprintf(f, "%s", move_buffer);
13813             linelen += movelen;
13814         }
13815
13816         i++;
13817     }
13818
13819     /* Start a new line */
13820     if (linelen > 0) fprintf(f, "\n");
13821
13822     /* Print comments after last move */
13823     if (commentList[i] != NULL) {
13824         fprintf(f, "%s\n", commentList[i]);
13825     }
13826
13827     /* Print result */
13828     if (gameInfo.resultDetails != NULL &&
13829         gameInfo.resultDetails[0] != NULLCHAR) {
13830         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13831         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13832            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13833             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13834         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13835     } else {
13836         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13837     }
13838 }
13839
13840 /* Save game in PGN style and close the file */
13841 int
13842 SaveGamePGN (FILE *f)
13843 {
13844     SaveGamePGN2(f);
13845     fclose(f);
13846     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13847     return TRUE;
13848 }
13849
13850 /* Save game in old style and close the file */
13851 int
13852 SaveGameOldStyle (FILE *f)
13853 {
13854     int i, offset;
13855     time_t tm;
13856
13857     tm = time((time_t *) NULL);
13858
13859     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13860     PrintOpponents(f);
13861
13862     if (backwardMostMove > 0 || startedFromSetupPosition) {
13863         fprintf(f, "\n[--------------\n");
13864         PrintPosition(f, backwardMostMove);
13865         fprintf(f, "--------------]\n");
13866     } else {
13867         fprintf(f, "\n");
13868     }
13869
13870     i = backwardMostMove;
13871     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13872
13873     while (i < forwardMostMove) {
13874         if (commentList[i] != NULL) {
13875             fprintf(f, "[%s]\n", commentList[i]);
13876         }
13877
13878         if ((i % 2) == 1) {
13879             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13880             i++;
13881         } else {
13882             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13883             i++;
13884             if (commentList[i] != NULL) {
13885                 fprintf(f, "\n");
13886                 continue;
13887             }
13888             if (i >= forwardMostMove) {
13889                 fprintf(f, "\n");
13890                 break;
13891             }
13892             fprintf(f, "%s\n", parseList[i]);
13893             i++;
13894         }
13895     }
13896
13897     if (commentList[i] != NULL) {
13898         fprintf(f, "[%s]\n", commentList[i]);
13899     }
13900
13901     /* This isn't really the old style, but it's close enough */
13902     if (gameInfo.resultDetails != NULL &&
13903         gameInfo.resultDetails[0] != NULLCHAR) {
13904         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13905                 gameInfo.resultDetails);
13906     } else {
13907         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13908     }
13909
13910     fclose(f);
13911     return TRUE;
13912 }
13913
13914 /* Save the current game to open file f and close the file */
13915 int
13916 SaveGame (FILE *f, int dummy, char *dummy2)
13917 {
13918     if (gameMode == EditPosition) EditPositionDone(TRUE);
13919     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13920     if (appData.oldSaveStyle)
13921       return SaveGameOldStyle(f);
13922     else
13923       return SaveGamePGN(f);
13924 }
13925
13926 /* Save the current position to the given file */
13927 int
13928 SavePositionToFile (char *filename)
13929 {
13930     FILE *f;
13931     char buf[MSG_SIZ];
13932
13933     if (strcmp(filename, "-") == 0) {
13934         return SavePosition(stdout, 0, NULL);
13935     } else {
13936         f = fopen(filename, "a");
13937         if (f == NULL) {
13938             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13939             DisplayError(buf, errno);
13940             return FALSE;
13941         } else {
13942             safeStrCpy(buf, lastMsg, MSG_SIZ);
13943             DisplayMessage(_("Waiting for access to save file"), "");
13944             flock(fileno(f), LOCK_EX); // [HGM] lock
13945             DisplayMessage(_("Saving position"), "");
13946             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13947             SavePosition(f, 0, NULL);
13948             DisplayMessage(buf, "");
13949             return TRUE;
13950         }
13951     }
13952 }
13953
13954 /* Save the current position to the given open file and close the file */
13955 int
13956 SavePosition (FILE *f, int dummy, char *dummy2)
13957 {
13958     time_t tm;
13959     char *fen;
13960
13961     if (gameMode == EditPosition) EditPositionDone(TRUE);
13962     if (appData.oldSaveStyle) {
13963         tm = time((time_t *) NULL);
13964
13965         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13966         PrintOpponents(f);
13967         fprintf(f, "[--------------\n");
13968         PrintPosition(f, currentMove);
13969         fprintf(f, "--------------]\n");
13970     } else {
13971         fen = PositionToFEN(currentMove, NULL, 1);
13972         fprintf(f, "%s\n", fen);
13973         free(fen);
13974     }
13975     fclose(f);
13976     return TRUE;
13977 }
13978
13979 void
13980 ReloadCmailMsgEvent (int unregister)
13981 {
13982 #if !WIN32
13983     static char *inFilename = NULL;
13984     static char *outFilename;
13985     int i;
13986     struct stat inbuf, outbuf;
13987     int status;
13988
13989     /* Any registered moves are unregistered if unregister is set, */
13990     /* i.e. invoked by the signal handler */
13991     if (unregister) {
13992         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13993             cmailMoveRegistered[i] = FALSE;
13994             if (cmailCommentList[i] != NULL) {
13995                 free(cmailCommentList[i]);
13996                 cmailCommentList[i] = NULL;
13997             }
13998         }
13999         nCmailMovesRegistered = 0;
14000     }
14001
14002     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14003         cmailResult[i] = CMAIL_NOT_RESULT;
14004     }
14005     nCmailResults = 0;
14006
14007     if (inFilename == NULL) {
14008         /* Because the filenames are static they only get malloced once  */
14009         /* and they never get freed                                      */
14010         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14011         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14012
14013         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14014         sprintf(outFilename, "%s.out", appData.cmailGameName);
14015     }
14016
14017     status = stat(outFilename, &outbuf);
14018     if (status < 0) {
14019         cmailMailedMove = FALSE;
14020     } else {
14021         status = stat(inFilename, &inbuf);
14022         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14023     }
14024
14025     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14026        counts the games, notes how each one terminated, etc.
14027
14028        It would be nice to remove this kludge and instead gather all
14029        the information while building the game list.  (And to keep it
14030        in the game list nodes instead of having a bunch of fixed-size
14031        parallel arrays.)  Note this will require getting each game's
14032        termination from the PGN tags, as the game list builder does
14033        not process the game moves.  --mann
14034        */
14035     cmailMsgLoaded = TRUE;
14036     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14037
14038     /* Load first game in the file or popup game menu */
14039     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14040
14041 #endif /* !WIN32 */
14042     return;
14043 }
14044
14045 int
14046 RegisterMove ()
14047 {
14048     FILE *f;
14049     char string[MSG_SIZ];
14050
14051     if (   cmailMailedMove
14052         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14053         return TRUE;            /* Allow free viewing  */
14054     }
14055
14056     /* Unregister move to ensure that we don't leave RegisterMove        */
14057     /* with the move registered when the conditions for registering no   */
14058     /* longer hold                                                       */
14059     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14060         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14061         nCmailMovesRegistered --;
14062
14063         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14064           {
14065               free(cmailCommentList[lastLoadGameNumber - 1]);
14066               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14067           }
14068     }
14069
14070     if (cmailOldMove == -1) {
14071         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14072         return FALSE;
14073     }
14074
14075     if (currentMove > cmailOldMove + 1) {
14076         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14077         return FALSE;
14078     }
14079
14080     if (currentMove < cmailOldMove) {
14081         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14082         return FALSE;
14083     }
14084
14085     if (forwardMostMove > currentMove) {
14086         /* Silently truncate extra moves */
14087         TruncateGame();
14088     }
14089
14090     if (   (currentMove == cmailOldMove + 1)
14091         || (   (currentMove == cmailOldMove)
14092             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14093                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14094         if (gameInfo.result != GameUnfinished) {
14095             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14096         }
14097
14098         if (commentList[currentMove] != NULL) {
14099             cmailCommentList[lastLoadGameNumber - 1]
14100               = StrSave(commentList[currentMove]);
14101         }
14102         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14103
14104         if (appData.debugMode)
14105           fprintf(debugFP, "Saving %s for game %d\n",
14106                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14107
14108         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14109
14110         f = fopen(string, "w");
14111         if (appData.oldSaveStyle) {
14112             SaveGameOldStyle(f); /* also closes the file */
14113
14114             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14115             f = fopen(string, "w");
14116             SavePosition(f, 0, NULL); /* also closes the file */
14117         } else {
14118             fprintf(f, "{--------------\n");
14119             PrintPosition(f, currentMove);
14120             fprintf(f, "--------------}\n\n");
14121
14122             SaveGame(f, 0, NULL); /* also closes the file*/
14123         }
14124
14125         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14126         nCmailMovesRegistered ++;
14127     } else if (nCmailGames == 1) {
14128         DisplayError(_("You have not made a move yet"), 0);
14129         return FALSE;
14130     }
14131
14132     return TRUE;
14133 }
14134
14135 void
14136 MailMoveEvent ()
14137 {
14138 #if !WIN32
14139     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14140     FILE *commandOutput;
14141     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14142     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14143     int nBuffers;
14144     int i;
14145     int archived;
14146     char *arcDir;
14147
14148     if (! cmailMsgLoaded) {
14149         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14150         return;
14151     }
14152
14153     if (nCmailGames == nCmailResults) {
14154         DisplayError(_("No unfinished games"), 0);
14155         return;
14156     }
14157
14158 #if CMAIL_PROHIBIT_REMAIL
14159     if (cmailMailedMove) {
14160       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);
14161         DisplayError(msg, 0);
14162         return;
14163     }
14164 #endif
14165
14166     if (! (cmailMailedMove || RegisterMove())) return;
14167
14168     if (   cmailMailedMove
14169         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14170       snprintf(string, MSG_SIZ, partCommandString,
14171                appData.debugMode ? " -v" : "", appData.cmailGameName);
14172         commandOutput = popen(string, "r");
14173
14174         if (commandOutput == NULL) {
14175             DisplayError(_("Failed to invoke cmail"), 0);
14176         } else {
14177             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14178                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14179             }
14180             if (nBuffers > 1) {
14181                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14182                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14183                 nBytes = MSG_SIZ - 1;
14184             } else {
14185                 (void) memcpy(msg, buffer, nBytes);
14186             }
14187             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14188
14189             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14190                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14191
14192                 archived = TRUE;
14193                 for (i = 0; i < nCmailGames; i ++) {
14194                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14195                         archived = FALSE;
14196                     }
14197                 }
14198                 if (   archived
14199                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14200                         != NULL)) {
14201                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14202                            arcDir,
14203                            appData.cmailGameName,
14204                            gameInfo.date);
14205                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14206                     cmailMsgLoaded = FALSE;
14207                 }
14208             }
14209
14210             DisplayInformation(msg);
14211             pclose(commandOutput);
14212         }
14213     } else {
14214         if ((*cmailMsg) != '\0') {
14215             DisplayInformation(cmailMsg);
14216         }
14217     }
14218
14219     return;
14220 #endif /* !WIN32 */
14221 }
14222
14223 char *
14224 CmailMsg ()
14225 {
14226 #if WIN32
14227     return NULL;
14228 #else
14229     int  prependComma = 0;
14230     char number[5];
14231     char string[MSG_SIZ];       /* Space for game-list */
14232     int  i;
14233
14234     if (!cmailMsgLoaded) return "";
14235
14236     if (cmailMailedMove) {
14237       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14238     } else {
14239         /* Create a list of games left */
14240       snprintf(string, MSG_SIZ, "[");
14241         for (i = 0; i < nCmailGames; i ++) {
14242             if (! (   cmailMoveRegistered[i]
14243                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14244                 if (prependComma) {
14245                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14246                 } else {
14247                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14248                     prependComma = 1;
14249                 }
14250
14251                 strcat(string, number);
14252             }
14253         }
14254         strcat(string, "]");
14255
14256         if (nCmailMovesRegistered + nCmailResults == 0) {
14257             switch (nCmailGames) {
14258               case 1:
14259                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14260                 break;
14261
14262               case 2:
14263                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14264                 break;
14265
14266               default:
14267                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14268                          nCmailGames);
14269                 break;
14270             }
14271         } else {
14272             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14273               case 1:
14274                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14275                          string);
14276                 break;
14277
14278               case 0:
14279                 if (nCmailResults == nCmailGames) {
14280                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14281                 } else {
14282                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14283                 }
14284                 break;
14285
14286               default:
14287                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14288                          string);
14289             }
14290         }
14291     }
14292     return cmailMsg;
14293 #endif /* WIN32 */
14294 }
14295
14296 void
14297 ResetGameEvent ()
14298 {
14299     if (gameMode == Training)
14300       SetTrainingModeOff();
14301
14302     Reset(TRUE, TRUE);
14303     cmailMsgLoaded = FALSE;
14304     if (appData.icsActive) {
14305       SendToICS(ics_prefix);
14306       SendToICS("refresh\n");
14307     }
14308 }
14309
14310 void
14311 ExitEvent (int status)
14312 {
14313     exiting++;
14314     if (exiting > 2) {
14315       /* Give up on clean exit */
14316       exit(status);
14317     }
14318     if (exiting > 1) {
14319       /* Keep trying for clean exit */
14320       return;
14321     }
14322
14323     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14324     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14325
14326     if (telnetISR != NULL) {
14327       RemoveInputSource(telnetISR);
14328     }
14329     if (icsPR != NoProc) {
14330       DestroyChildProcess(icsPR, TRUE);
14331     }
14332
14333     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14334     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14335
14336     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14337     /* make sure this other one finishes before killing it!                  */
14338     if(endingGame) { int count = 0;
14339         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14340         while(endingGame && count++ < 10) DoSleep(1);
14341         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14342     }
14343
14344     /* Kill off chess programs */
14345     if (first.pr != NoProc) {
14346         ExitAnalyzeMode();
14347
14348         DoSleep( appData.delayBeforeQuit );
14349         SendToProgram("quit\n", &first);
14350         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14351     }
14352     if (second.pr != NoProc) {
14353         DoSleep( appData.delayBeforeQuit );
14354         SendToProgram("quit\n", &second);
14355         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14356     }
14357     if (first.isr != NULL) {
14358         RemoveInputSource(first.isr);
14359     }
14360     if (second.isr != NULL) {
14361         RemoveInputSource(second.isr);
14362     }
14363
14364     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14365     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14366
14367     ShutDownFrontEnd();
14368     exit(status);
14369 }
14370
14371 void
14372 PauseEngine (ChessProgramState *cps)
14373 {
14374     SendToProgram("pause\n", cps);
14375     cps->pause = 2;
14376 }
14377
14378 void
14379 UnPauseEngine (ChessProgramState *cps)
14380 {
14381     SendToProgram("resume\n", cps);
14382     cps->pause = 1;
14383 }
14384
14385 void
14386 PauseEvent ()
14387 {
14388     if (appData.debugMode)
14389         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14390     if (pausing) {
14391         pausing = FALSE;
14392         ModeHighlight();
14393         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14394             StartClocks();
14395             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14396                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14397                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14398             }
14399             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14400             HandleMachineMove(stashedInputMove, stalledEngine);
14401             stalledEngine = NULL;
14402             return;
14403         }
14404         if (gameMode == MachinePlaysWhite ||
14405             gameMode == TwoMachinesPlay   ||
14406             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14407             if(first.pause)  UnPauseEngine(&first);
14408             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14409             if(second.pause) UnPauseEngine(&second);
14410             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14411             StartClocks();
14412         } else {
14413             DisplayBothClocks();
14414         }
14415         if (gameMode == PlayFromGameFile) {
14416             if (appData.timeDelay >= 0)
14417                 AutoPlayGameLoop();
14418         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14419             Reset(FALSE, TRUE);
14420             SendToICS(ics_prefix);
14421             SendToICS("refresh\n");
14422         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14423             ForwardInner(forwardMostMove);
14424         }
14425         pauseExamInvalid = FALSE;
14426     } else {
14427         switch (gameMode) {
14428           default:
14429             return;
14430           case IcsExamining:
14431             pauseExamForwardMostMove = forwardMostMove;
14432             pauseExamInvalid = FALSE;
14433             /* fall through */
14434           case IcsObserving:
14435           case IcsPlayingWhite:
14436           case IcsPlayingBlack:
14437             pausing = TRUE;
14438             ModeHighlight();
14439             return;
14440           case PlayFromGameFile:
14441             (void) StopLoadGameTimer();
14442             pausing = TRUE;
14443             ModeHighlight();
14444             break;
14445           case BeginningOfGame:
14446             if (appData.icsActive) return;
14447             /* else fall through */
14448           case MachinePlaysWhite:
14449           case MachinePlaysBlack:
14450           case TwoMachinesPlay:
14451             if (forwardMostMove == 0)
14452               return;           /* don't pause if no one has moved */
14453             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14454                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14455                 if(onMove->pause) {           // thinking engine can be paused
14456                     PauseEngine(onMove);      // do it
14457                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14458                         PauseEngine(onMove->other);
14459                     else
14460                         SendToProgram("easy\n", onMove->other);
14461                     StopClocks();
14462                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14463             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14464                 if(first.pause) {
14465                     PauseEngine(&first);
14466                     StopClocks();
14467                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14468             } else { // human on move, pause pondering by either method
14469                 if(first.pause)
14470                     PauseEngine(&first);
14471                 else if(appData.ponderNextMove)
14472                     SendToProgram("easy\n", &first);
14473                 StopClocks();
14474             }
14475             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14476           case AnalyzeMode:
14477             pausing = TRUE;
14478             ModeHighlight();
14479             break;
14480         }
14481     }
14482 }
14483
14484 void
14485 EditCommentEvent ()
14486 {
14487     char title[MSG_SIZ];
14488
14489     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14490       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14491     } else {
14492       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14493                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14494                parseList[currentMove - 1]);
14495     }
14496
14497     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14498 }
14499
14500
14501 void
14502 EditTagsEvent ()
14503 {
14504     char *tags = PGNTags(&gameInfo);
14505     bookUp = FALSE;
14506     EditTagsPopUp(tags, NULL);
14507     free(tags);
14508 }
14509
14510 void
14511 ToggleSecond ()
14512 {
14513   if(second.analyzing) {
14514     SendToProgram("exit\n", &second);
14515     second.analyzing = FALSE;
14516   } else {
14517     if (second.pr == NoProc) StartChessProgram(&second);
14518     InitChessProgram(&second, FALSE);
14519     FeedMovesToProgram(&second, currentMove);
14520
14521     SendToProgram("analyze\n", &second);
14522     second.analyzing = TRUE;
14523   }
14524 }
14525
14526 /* Toggle ShowThinking */
14527 void
14528 ToggleShowThinking()
14529 {
14530   appData.showThinking = !appData.showThinking;
14531   ShowThinkingEvent();
14532 }
14533
14534 int
14535 AnalyzeModeEvent ()
14536 {
14537     char buf[MSG_SIZ];
14538
14539     if (!first.analysisSupport) {
14540       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14541       DisplayError(buf, 0);
14542       return 0;
14543     }
14544     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14545     if (appData.icsActive) {
14546         if (gameMode != IcsObserving) {
14547           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14548             DisplayError(buf, 0);
14549             /* secure check */
14550             if (appData.icsEngineAnalyze) {
14551                 if (appData.debugMode)
14552                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14553                 ExitAnalyzeMode();
14554                 ModeHighlight();
14555             }
14556             return 0;
14557         }
14558         /* if enable, user wants to disable icsEngineAnalyze */
14559         if (appData.icsEngineAnalyze) {
14560                 ExitAnalyzeMode();
14561                 ModeHighlight();
14562                 return 0;
14563         }
14564         appData.icsEngineAnalyze = TRUE;
14565         if (appData.debugMode)
14566             fprintf(debugFP, "ICS engine analyze starting... \n");
14567     }
14568
14569     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14570     if (appData.noChessProgram || gameMode == AnalyzeMode)
14571       return 0;
14572
14573     if (gameMode != AnalyzeFile) {
14574         if (!appData.icsEngineAnalyze) {
14575                EditGameEvent();
14576                if (gameMode != EditGame) return 0;
14577         }
14578         if (!appData.showThinking) ToggleShowThinking();
14579         ResurrectChessProgram();
14580         SendToProgram("analyze\n", &first);
14581         first.analyzing = TRUE;
14582         /*first.maybeThinking = TRUE;*/
14583         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14584         EngineOutputPopUp();
14585     }
14586     if (!appData.icsEngineAnalyze) {
14587         gameMode = AnalyzeMode;
14588         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14589     }
14590     pausing = FALSE;
14591     ModeHighlight();
14592     SetGameInfo();
14593
14594     StartAnalysisClock();
14595     GetTimeMark(&lastNodeCountTime);
14596     lastNodeCount = 0;
14597     return 1;
14598 }
14599
14600 void
14601 AnalyzeFileEvent ()
14602 {
14603     if (appData.noChessProgram || gameMode == AnalyzeFile)
14604       return;
14605
14606     if (!first.analysisSupport) {
14607       char buf[MSG_SIZ];
14608       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14609       DisplayError(buf, 0);
14610       return;
14611     }
14612
14613     if (gameMode != AnalyzeMode) {
14614         keepInfo = 1; // mere annotating should not alter PGN tags
14615         EditGameEvent();
14616         keepInfo = 0;
14617         if (gameMode != EditGame) return;
14618         if (!appData.showThinking) ToggleShowThinking();
14619         ResurrectChessProgram();
14620         SendToProgram("analyze\n", &first);
14621         first.analyzing = TRUE;
14622         /*first.maybeThinking = TRUE;*/
14623         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14624         EngineOutputPopUp();
14625     }
14626     gameMode = AnalyzeFile;
14627     pausing = FALSE;
14628     ModeHighlight();
14629
14630     StartAnalysisClock();
14631     GetTimeMark(&lastNodeCountTime);
14632     lastNodeCount = 0;
14633     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14634     AnalysisPeriodicEvent(1);
14635 }
14636
14637 void
14638 MachineWhiteEvent ()
14639 {
14640     char buf[MSG_SIZ];
14641     char *bookHit = NULL;
14642
14643     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14644       return;
14645
14646
14647     if (gameMode == PlayFromGameFile ||
14648         gameMode == TwoMachinesPlay  ||
14649         gameMode == Training         ||
14650         gameMode == AnalyzeMode      ||
14651         gameMode == EndOfGame)
14652         EditGameEvent();
14653
14654     if (gameMode == EditPosition)
14655         EditPositionDone(TRUE);
14656
14657     if (!WhiteOnMove(currentMove)) {
14658         DisplayError(_("It is not White's turn"), 0);
14659         return;
14660     }
14661
14662     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14663       ExitAnalyzeMode();
14664
14665     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14666         gameMode == AnalyzeFile)
14667         TruncateGame();
14668
14669     ResurrectChessProgram();    /* in case it isn't running */
14670     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14671         gameMode = MachinePlaysWhite;
14672         ResetClocks();
14673     } else
14674     gameMode = MachinePlaysWhite;
14675     pausing = FALSE;
14676     ModeHighlight();
14677     SetGameInfo();
14678     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14679     DisplayTitle(buf);
14680     if (first.sendName) {
14681       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14682       SendToProgram(buf, &first);
14683     }
14684     if (first.sendTime) {
14685       if (first.useColors) {
14686         SendToProgram("black\n", &first); /*gnu kludge*/
14687       }
14688       SendTimeRemaining(&first, TRUE);
14689     }
14690     if (first.useColors) {
14691       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14692     }
14693     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14694     SetMachineThinkingEnables();
14695     first.maybeThinking = TRUE;
14696     StartClocks();
14697     firstMove = FALSE;
14698
14699     if (appData.autoFlipView && !flipView) {
14700       flipView = !flipView;
14701       DrawPosition(FALSE, NULL);
14702       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14703     }
14704
14705     if(bookHit) { // [HGM] book: simulate book reply
14706         static char bookMove[MSG_SIZ]; // a bit generous?
14707
14708         programStats.nodes = programStats.depth = programStats.time =
14709         programStats.score = programStats.got_only_move = 0;
14710         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14711
14712         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14713         strcat(bookMove, bookHit);
14714         HandleMachineMove(bookMove, &first);
14715     }
14716 }
14717
14718 void
14719 MachineBlackEvent ()
14720 {
14721   char buf[MSG_SIZ];
14722   char *bookHit = NULL;
14723
14724     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14725         return;
14726
14727
14728     if (gameMode == PlayFromGameFile ||
14729         gameMode == TwoMachinesPlay  ||
14730         gameMode == Training         ||
14731         gameMode == AnalyzeMode      ||
14732         gameMode == EndOfGame)
14733         EditGameEvent();
14734
14735     if (gameMode == EditPosition)
14736         EditPositionDone(TRUE);
14737
14738     if (WhiteOnMove(currentMove)) {
14739         DisplayError(_("It is not Black's turn"), 0);
14740         return;
14741     }
14742
14743     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14744       ExitAnalyzeMode();
14745
14746     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14747         gameMode == AnalyzeFile)
14748         TruncateGame();
14749
14750     ResurrectChessProgram();    /* in case it isn't running */
14751     gameMode = MachinePlaysBlack;
14752     pausing = FALSE;
14753     ModeHighlight();
14754     SetGameInfo();
14755     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14756     DisplayTitle(buf);
14757     if (first.sendName) {
14758       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14759       SendToProgram(buf, &first);
14760     }
14761     if (first.sendTime) {
14762       if (first.useColors) {
14763         SendToProgram("white\n", &first); /*gnu kludge*/
14764       }
14765       SendTimeRemaining(&first, FALSE);
14766     }
14767     if (first.useColors) {
14768       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14769     }
14770     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14771     SetMachineThinkingEnables();
14772     first.maybeThinking = TRUE;
14773     StartClocks();
14774
14775     if (appData.autoFlipView && flipView) {
14776       flipView = !flipView;
14777       DrawPosition(FALSE, NULL);
14778       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14779     }
14780     if(bookHit) { // [HGM] book: simulate book reply
14781         static char bookMove[MSG_SIZ]; // a bit generous?
14782
14783         programStats.nodes = programStats.depth = programStats.time =
14784         programStats.score = programStats.got_only_move = 0;
14785         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14786
14787         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14788         strcat(bookMove, bookHit);
14789         HandleMachineMove(bookMove, &first);
14790     }
14791 }
14792
14793
14794 void
14795 DisplayTwoMachinesTitle ()
14796 {
14797     char buf[MSG_SIZ];
14798     if (appData.matchGames > 0) {
14799         if(appData.tourneyFile[0]) {
14800           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14801                    gameInfo.white, _("vs."), gameInfo.black,
14802                    nextGame+1, appData.matchGames+1,
14803                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14804         } else
14805         if (first.twoMachinesColor[0] == 'w') {
14806           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14807                    gameInfo.white, _("vs."),  gameInfo.black,
14808                    first.matchWins, second.matchWins,
14809                    matchGame - 1 - (first.matchWins + second.matchWins));
14810         } else {
14811           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14812                    gameInfo.white, _("vs."), gameInfo.black,
14813                    second.matchWins, first.matchWins,
14814                    matchGame - 1 - (first.matchWins + second.matchWins));
14815         }
14816     } else {
14817       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14818     }
14819     DisplayTitle(buf);
14820 }
14821
14822 void
14823 SettingsMenuIfReady ()
14824 {
14825   if (second.lastPing != second.lastPong) {
14826     DisplayMessage("", _("Waiting for second chess program"));
14827     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14828     return;
14829   }
14830   ThawUI();
14831   DisplayMessage("", "");
14832   SettingsPopUp(&second);
14833 }
14834
14835 int
14836 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14837 {
14838     char buf[MSG_SIZ];
14839     if (cps->pr == NoProc) {
14840         StartChessProgram(cps);
14841         if (cps->protocolVersion == 1) {
14842           retry();
14843           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14844         } else {
14845           /* kludge: allow timeout for initial "feature" command */
14846           if(retry != TwoMachinesEventIfReady) FreezeUI();
14847           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14848           DisplayMessage("", buf);
14849           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14850         }
14851         return 1;
14852     }
14853     return 0;
14854 }
14855
14856 void
14857 TwoMachinesEvent P((void))
14858 {
14859     int i;
14860     char buf[MSG_SIZ];
14861     ChessProgramState *onmove;
14862     char *bookHit = NULL;
14863     static int stalling = 0;
14864     TimeMark now;
14865     long wait;
14866
14867     if (appData.noChessProgram) return;
14868
14869     switch (gameMode) {
14870       case TwoMachinesPlay:
14871         return;
14872       case MachinePlaysWhite:
14873       case MachinePlaysBlack:
14874         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14875             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14876             return;
14877         }
14878         /* fall through */
14879       case BeginningOfGame:
14880       case PlayFromGameFile:
14881       case EndOfGame:
14882         EditGameEvent();
14883         if (gameMode != EditGame) return;
14884         break;
14885       case EditPosition:
14886         EditPositionDone(TRUE);
14887         break;
14888       case AnalyzeMode:
14889       case AnalyzeFile:
14890         ExitAnalyzeMode();
14891         break;
14892       case EditGame:
14893       default:
14894         break;
14895     }
14896
14897 //    forwardMostMove = currentMove;
14898     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14899     startingEngine = TRUE;
14900
14901     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14902
14903     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14904     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14905       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14906       return;
14907     }
14908     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14909
14910     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14911                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14912         startingEngine = matchMode = FALSE;
14913         DisplayError("second engine does not play this", 0);
14914         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14915         EditGameEvent(); // switch back to EditGame mode
14916         return;
14917     }
14918
14919     if(!stalling) {
14920       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14921       SendToProgram("force\n", &second);
14922       stalling = 1;
14923       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14924       return;
14925     }
14926     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14927     if(appData.matchPause>10000 || appData.matchPause<10)
14928                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14929     wait = SubtractTimeMarks(&now, &pauseStart);
14930     if(wait < appData.matchPause) {
14931         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14932         return;
14933     }
14934     // we are now committed to starting the game
14935     stalling = 0;
14936     DisplayMessage("", "");
14937     if (startedFromSetupPosition) {
14938         SendBoard(&second, backwardMostMove);
14939     if (appData.debugMode) {
14940         fprintf(debugFP, "Two Machines\n");
14941     }
14942     }
14943     for (i = backwardMostMove; i < forwardMostMove; i++) {
14944         SendMoveToProgram(i, &second);
14945     }
14946
14947     gameMode = TwoMachinesPlay;
14948     pausing = startingEngine = FALSE;
14949     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14950     SetGameInfo();
14951     DisplayTwoMachinesTitle();
14952     firstMove = TRUE;
14953     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14954         onmove = &first;
14955     } else {
14956         onmove = &second;
14957     }
14958     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14959     SendToProgram(first.computerString, &first);
14960     if (first.sendName) {
14961       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14962       SendToProgram(buf, &first);
14963     }
14964     SendToProgram(second.computerString, &second);
14965     if (second.sendName) {
14966       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14967       SendToProgram(buf, &second);
14968     }
14969
14970     ResetClocks();
14971     if (!first.sendTime || !second.sendTime) {
14972         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14973         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14974     }
14975     if (onmove->sendTime) {
14976       if (onmove->useColors) {
14977         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14978       }
14979       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14980     }
14981     if (onmove->useColors) {
14982       SendToProgram(onmove->twoMachinesColor, onmove);
14983     }
14984     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14985 //    SendToProgram("go\n", onmove);
14986     onmove->maybeThinking = TRUE;
14987     SetMachineThinkingEnables();
14988
14989     StartClocks();
14990
14991     if(bookHit) { // [HGM] book: simulate book reply
14992         static char bookMove[MSG_SIZ]; // a bit generous?
14993
14994         programStats.nodes = programStats.depth = programStats.time =
14995         programStats.score = programStats.got_only_move = 0;
14996         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14997
14998         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14999         strcat(bookMove, bookHit);
15000         savedMessage = bookMove; // args for deferred call
15001         savedState = onmove;
15002         ScheduleDelayedEvent(DeferredBookMove, 1);
15003     }
15004 }
15005
15006 void
15007 TrainingEvent ()
15008 {
15009     if (gameMode == Training) {
15010       SetTrainingModeOff();
15011       gameMode = PlayFromGameFile;
15012       DisplayMessage("", _("Training mode off"));
15013     } else {
15014       gameMode = Training;
15015       animateTraining = appData.animate;
15016
15017       /* make sure we are not already at the end of the game */
15018       if (currentMove < forwardMostMove) {
15019         SetTrainingModeOn();
15020         DisplayMessage("", _("Training mode on"));
15021       } else {
15022         gameMode = PlayFromGameFile;
15023         DisplayError(_("Already at end of game"), 0);
15024       }
15025     }
15026     ModeHighlight();
15027 }
15028
15029 void
15030 IcsClientEvent ()
15031 {
15032     if (!appData.icsActive) return;
15033     switch (gameMode) {
15034       case IcsPlayingWhite:
15035       case IcsPlayingBlack:
15036       case IcsObserving:
15037       case IcsIdle:
15038       case BeginningOfGame:
15039       case IcsExamining:
15040         return;
15041
15042       case EditGame:
15043         break;
15044
15045       case EditPosition:
15046         EditPositionDone(TRUE);
15047         break;
15048
15049       case AnalyzeMode:
15050       case AnalyzeFile:
15051         ExitAnalyzeMode();
15052         break;
15053
15054       default:
15055         EditGameEvent();
15056         break;
15057     }
15058
15059     gameMode = IcsIdle;
15060     ModeHighlight();
15061     return;
15062 }
15063
15064 void
15065 EditGameEvent ()
15066 {
15067     int i;
15068
15069     switch (gameMode) {
15070       case Training:
15071         SetTrainingModeOff();
15072         break;
15073       case MachinePlaysWhite:
15074       case MachinePlaysBlack:
15075       case BeginningOfGame:
15076         SendToProgram("force\n", &first);
15077         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15078             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15079                 char buf[MSG_SIZ];
15080                 abortEngineThink = TRUE;
15081                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15082                 SendToProgram(buf, &first);
15083                 DisplayMessage("Aborting engine think", "");
15084                 FreezeUI();
15085             }
15086         }
15087         SetUserThinkingEnables();
15088         break;
15089       case PlayFromGameFile:
15090         (void) StopLoadGameTimer();
15091         if (gameFileFP != NULL) {
15092             gameFileFP = NULL;
15093         }
15094         break;
15095       case EditPosition:
15096         EditPositionDone(TRUE);
15097         break;
15098       case AnalyzeMode:
15099       case AnalyzeFile:
15100         ExitAnalyzeMode();
15101         SendToProgram("force\n", &first);
15102         break;
15103       case TwoMachinesPlay:
15104         GameEnds(EndOfFile, NULL, GE_PLAYER);
15105         ResurrectChessProgram();
15106         SetUserThinkingEnables();
15107         break;
15108       case EndOfGame:
15109         ResurrectChessProgram();
15110         break;
15111       case IcsPlayingBlack:
15112       case IcsPlayingWhite:
15113         DisplayError(_("Warning: You are still playing a game"), 0);
15114         break;
15115       case IcsObserving:
15116         DisplayError(_("Warning: You are still observing a game"), 0);
15117         break;
15118       case IcsExamining:
15119         DisplayError(_("Warning: You are still examining a game"), 0);
15120         break;
15121       case IcsIdle:
15122         break;
15123       case EditGame:
15124       default:
15125         return;
15126     }
15127
15128     pausing = FALSE;
15129     StopClocks();
15130     first.offeredDraw = second.offeredDraw = 0;
15131
15132     if (gameMode == PlayFromGameFile) {
15133         whiteTimeRemaining = timeRemaining[0][currentMove];
15134         blackTimeRemaining = timeRemaining[1][currentMove];
15135         DisplayTitle("");
15136     }
15137
15138     if (gameMode == MachinePlaysWhite ||
15139         gameMode == MachinePlaysBlack ||
15140         gameMode == TwoMachinesPlay ||
15141         gameMode == EndOfGame) {
15142         i = forwardMostMove;
15143         while (i > currentMove) {
15144             SendToProgram("undo\n", &first);
15145             i--;
15146         }
15147         if(!adjustedClock) {
15148         whiteTimeRemaining = timeRemaining[0][currentMove];
15149         blackTimeRemaining = timeRemaining[1][currentMove];
15150         DisplayBothClocks();
15151         }
15152         if (whiteFlag || blackFlag) {
15153             whiteFlag = blackFlag = 0;
15154         }
15155         DisplayTitle("");
15156     }
15157
15158     gameMode = EditGame;
15159     ModeHighlight();
15160     SetGameInfo();
15161 }
15162
15163
15164 void
15165 EditPositionEvent ()
15166 {
15167     if (gameMode == EditPosition) {
15168         EditGameEvent();
15169         return;
15170     }
15171
15172     EditGameEvent();
15173     if (gameMode != EditGame) return;
15174
15175     gameMode = EditPosition;
15176     ModeHighlight();
15177     SetGameInfo();
15178     if (currentMove > 0)
15179       CopyBoard(boards[0], boards[currentMove]);
15180
15181     blackPlaysFirst = !WhiteOnMove(currentMove);
15182     ResetClocks();
15183     currentMove = forwardMostMove = backwardMostMove = 0;
15184     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15185     DisplayMove(-1);
15186     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15187 }
15188
15189 void
15190 ExitAnalyzeMode ()
15191 {
15192     /* [DM] icsEngineAnalyze - possible call from other functions */
15193     if (appData.icsEngineAnalyze) {
15194         appData.icsEngineAnalyze = FALSE;
15195
15196         DisplayMessage("",_("Close ICS engine analyze..."));
15197     }
15198     if (first.analysisSupport && first.analyzing) {
15199       SendToBoth("exit\n");
15200       first.analyzing = second.analyzing = FALSE;
15201     }
15202     thinkOutput[0] = NULLCHAR;
15203 }
15204
15205 void
15206 EditPositionDone (Boolean fakeRights)
15207 {
15208     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15209
15210     startedFromSetupPosition = TRUE;
15211     InitChessProgram(&first, FALSE);
15212     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15213       boards[0][EP_STATUS] = EP_NONE;
15214       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15215       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15216         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15217         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15218       } else boards[0][CASTLING][2] = NoRights;
15219       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15220         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15221         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15222       } else boards[0][CASTLING][5] = NoRights;
15223       if(gameInfo.variant == VariantSChess) {
15224         int i;
15225         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15226           boards[0][VIRGIN][i] = 0;
15227           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15228           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15229         }
15230       }
15231     }
15232     SendToProgram("force\n", &first);
15233     if (blackPlaysFirst) {
15234         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15235         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15236         currentMove = forwardMostMove = backwardMostMove = 1;
15237         CopyBoard(boards[1], boards[0]);
15238     } else {
15239         currentMove = forwardMostMove = backwardMostMove = 0;
15240     }
15241     SendBoard(&first, forwardMostMove);
15242     if (appData.debugMode) {
15243         fprintf(debugFP, "EditPosDone\n");
15244     }
15245     DisplayTitle("");
15246     DisplayMessage("", "");
15247     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15248     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15249     gameMode = EditGame;
15250     ModeHighlight();
15251     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15252     ClearHighlights(); /* [AS] */
15253 }
15254
15255 /* Pause for `ms' milliseconds */
15256 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15257 void
15258 TimeDelay (long ms)
15259 {
15260     TimeMark m1, m2;
15261
15262     GetTimeMark(&m1);
15263     do {
15264         GetTimeMark(&m2);
15265     } while (SubtractTimeMarks(&m2, &m1) < ms);
15266 }
15267
15268 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15269 void
15270 SendMultiLineToICS (char *buf)
15271 {
15272     char temp[MSG_SIZ+1], *p;
15273     int len;
15274
15275     len = strlen(buf);
15276     if (len > MSG_SIZ)
15277       len = MSG_SIZ;
15278
15279     strncpy(temp, buf, len);
15280     temp[len] = 0;
15281
15282     p = temp;
15283     while (*p) {
15284         if (*p == '\n' || *p == '\r')
15285           *p = ' ';
15286         ++p;
15287     }
15288
15289     strcat(temp, "\n");
15290     SendToICS(temp);
15291     SendToPlayer(temp, strlen(temp));
15292 }
15293
15294 void
15295 SetWhiteToPlayEvent ()
15296 {
15297     if (gameMode == EditPosition) {
15298         blackPlaysFirst = FALSE;
15299         DisplayBothClocks();    /* works because currentMove is 0 */
15300     } else if (gameMode == IcsExamining) {
15301         SendToICS(ics_prefix);
15302         SendToICS("tomove white\n");
15303     }
15304 }
15305
15306 void
15307 SetBlackToPlayEvent ()
15308 {
15309     if (gameMode == EditPosition) {
15310         blackPlaysFirst = TRUE;
15311         currentMove = 1;        /* kludge */
15312         DisplayBothClocks();
15313         currentMove = 0;
15314     } else if (gameMode == IcsExamining) {
15315         SendToICS(ics_prefix);
15316         SendToICS("tomove black\n");
15317     }
15318 }
15319
15320 void
15321 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15322 {
15323     char buf[MSG_SIZ];
15324     ChessSquare piece = boards[0][y][x];
15325     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15326     static int lastVariant;
15327
15328     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15329
15330     switch (selection) {
15331       case ClearBoard:
15332         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15333         MarkTargetSquares(1);
15334         CopyBoard(currentBoard, boards[0]);
15335         CopyBoard(menuBoard, initialPosition);
15336         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15337             SendToICS(ics_prefix);
15338             SendToICS("bsetup clear\n");
15339         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15340             SendToICS(ics_prefix);
15341             SendToICS("clearboard\n");
15342         } else {
15343             int nonEmpty = 0;
15344             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15345                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15346                 for (y = 0; y < BOARD_HEIGHT; y++) {
15347                     if (gameMode == IcsExamining) {
15348                         if (boards[currentMove][y][x] != EmptySquare) {
15349                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15350                                     AAA + x, ONE + y);
15351                             SendToICS(buf);
15352                         }
15353                     } else if(boards[0][y][x] != DarkSquare) {
15354                         if(boards[0][y][x] != p) nonEmpty++;
15355                         boards[0][y][x] = p;
15356                     }
15357                 }
15358             }
15359             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15360                 int r;
15361                 for(r = 0; r < BOARD_HEIGHT; r++) {
15362                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15363                     ChessSquare p = menuBoard[r][x];
15364                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15365                   }
15366                 }
15367                 DisplayMessage("Clicking clock again restores position", "");
15368                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15369                 if(!nonEmpty) { // asked to clear an empty board
15370                     CopyBoard(boards[0], menuBoard);
15371                 } else
15372                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15373                     CopyBoard(boards[0], initialPosition);
15374                 } else
15375                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15376                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15377                     CopyBoard(boards[0], erasedBoard);
15378                 } else
15379                     CopyBoard(erasedBoard, currentBoard);
15380
15381             }
15382         }
15383         if (gameMode == EditPosition) {
15384             DrawPosition(FALSE, boards[0]);
15385         }
15386         break;
15387
15388       case WhitePlay:
15389         SetWhiteToPlayEvent();
15390         break;
15391
15392       case BlackPlay:
15393         SetBlackToPlayEvent();
15394         break;
15395
15396       case EmptySquare:
15397         if (gameMode == IcsExamining) {
15398             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15399             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15400             SendToICS(buf);
15401         } else {
15402             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15403                 if(x == BOARD_LEFT-2) {
15404                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15405                     boards[0][y][1] = 0;
15406                 } else
15407                 if(x == BOARD_RGHT+1) {
15408                     if(y >= gameInfo.holdingsSize) break;
15409                     boards[0][y][BOARD_WIDTH-2] = 0;
15410                 } else break;
15411             }
15412             boards[0][y][x] = EmptySquare;
15413             DrawPosition(FALSE, boards[0]);
15414         }
15415         break;
15416
15417       case PromotePiece:
15418         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15419            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15420             selection = (ChessSquare) (PROMOTED(piece));
15421         } else if(piece == EmptySquare) selection = WhiteSilver;
15422         else selection = (ChessSquare)((int)piece - 1);
15423         goto defaultlabel;
15424
15425       case DemotePiece:
15426         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15427            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15428             selection = (ChessSquare) (DEMOTED(piece));
15429         } else if(piece == EmptySquare) selection = BlackSilver;
15430         else selection = (ChessSquare)((int)piece + 1);
15431         goto defaultlabel;
15432
15433       case WhiteQueen:
15434       case BlackQueen:
15435         if(gameInfo.variant == VariantShatranj ||
15436            gameInfo.variant == VariantXiangqi  ||
15437            gameInfo.variant == VariantCourier  ||
15438            gameInfo.variant == VariantASEAN    ||
15439            gameInfo.variant == VariantMakruk     )
15440             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15441         goto defaultlabel;
15442
15443       case WhiteKing:
15444       case BlackKing:
15445         if(gameInfo.variant == VariantXiangqi)
15446             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15447         if(gameInfo.variant == VariantKnightmate)
15448             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15449       default:
15450         defaultlabel:
15451         if (gameMode == IcsExamining) {
15452             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15453             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15454                      PieceToChar(selection), AAA + x, ONE + y);
15455             SendToICS(buf);
15456         } else {
15457             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15458                 int n;
15459                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15460                     n = PieceToNumber(selection - BlackPawn);
15461                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15462                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15463                     boards[0][BOARD_HEIGHT-1-n][1]++;
15464                 } else
15465                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15466                     n = PieceToNumber(selection);
15467                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15468                     boards[0][n][BOARD_WIDTH-1] = selection;
15469                     boards[0][n][BOARD_WIDTH-2]++;
15470                 }
15471             } else
15472             boards[0][y][x] = selection;
15473             DrawPosition(TRUE, boards[0]);
15474             ClearHighlights();
15475             fromX = fromY = -1;
15476         }
15477         break;
15478     }
15479 }
15480
15481
15482 void
15483 DropMenuEvent (ChessSquare selection, int x, int y)
15484 {
15485     ChessMove moveType;
15486
15487     switch (gameMode) {
15488       case IcsPlayingWhite:
15489       case MachinePlaysBlack:
15490         if (!WhiteOnMove(currentMove)) {
15491             DisplayMoveError(_("It is Black's turn"));
15492             return;
15493         }
15494         moveType = WhiteDrop;
15495         break;
15496       case IcsPlayingBlack:
15497       case MachinePlaysWhite:
15498         if (WhiteOnMove(currentMove)) {
15499             DisplayMoveError(_("It is White's turn"));
15500             return;
15501         }
15502         moveType = BlackDrop;
15503         break;
15504       case EditGame:
15505         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15506         break;
15507       default:
15508         return;
15509     }
15510
15511     if (moveType == BlackDrop && selection < BlackPawn) {
15512       selection = (ChessSquare) ((int) selection
15513                                  + (int) BlackPawn - (int) WhitePawn);
15514     }
15515     if (boards[currentMove][y][x] != EmptySquare) {
15516         DisplayMoveError(_("That square is occupied"));
15517         return;
15518     }
15519
15520     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15521 }
15522
15523 void
15524 AcceptEvent ()
15525 {
15526     /* Accept a pending offer of any kind from opponent */
15527
15528     if (appData.icsActive) {
15529         SendToICS(ics_prefix);
15530         SendToICS("accept\n");
15531     } else if (cmailMsgLoaded) {
15532         if (currentMove == cmailOldMove &&
15533             commentList[cmailOldMove] != NULL &&
15534             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15535                    "Black offers a draw" : "White offers a draw")) {
15536             TruncateGame();
15537             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15538             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15539         } else {
15540             DisplayError(_("There is no pending offer on this move"), 0);
15541             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15542         }
15543     } else {
15544         /* Not used for offers from chess program */
15545     }
15546 }
15547
15548 void
15549 DeclineEvent ()
15550 {
15551     /* Decline a pending offer of any kind from opponent */
15552
15553     if (appData.icsActive) {
15554         SendToICS(ics_prefix);
15555         SendToICS("decline\n");
15556     } else if (cmailMsgLoaded) {
15557         if (currentMove == cmailOldMove &&
15558             commentList[cmailOldMove] != NULL &&
15559             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15560                    "Black offers a draw" : "White offers a draw")) {
15561 #ifdef NOTDEF
15562             AppendComment(cmailOldMove, "Draw declined", TRUE);
15563             DisplayComment(cmailOldMove - 1, "Draw declined");
15564 #endif /*NOTDEF*/
15565         } else {
15566             DisplayError(_("There is no pending offer on this move"), 0);
15567         }
15568     } else {
15569         /* Not used for offers from chess program */
15570     }
15571 }
15572
15573 void
15574 RematchEvent ()
15575 {
15576     /* Issue ICS rematch command */
15577     if (appData.icsActive) {
15578         SendToICS(ics_prefix);
15579         SendToICS("rematch\n");
15580     }
15581 }
15582
15583 void
15584 CallFlagEvent ()
15585 {
15586     /* Call your opponent's flag (claim a win on time) */
15587     if (appData.icsActive) {
15588         SendToICS(ics_prefix);
15589         SendToICS("flag\n");
15590     } else {
15591         switch (gameMode) {
15592           default:
15593             return;
15594           case MachinePlaysWhite:
15595             if (whiteFlag) {
15596                 if (blackFlag)
15597                   GameEnds(GameIsDrawn, "Both players ran out of time",
15598                            GE_PLAYER);
15599                 else
15600                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15601             } else {
15602                 DisplayError(_("Your opponent is not out of time"), 0);
15603             }
15604             break;
15605           case MachinePlaysBlack:
15606             if (blackFlag) {
15607                 if (whiteFlag)
15608                   GameEnds(GameIsDrawn, "Both players ran out of time",
15609                            GE_PLAYER);
15610                 else
15611                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15612             } else {
15613                 DisplayError(_("Your opponent is not out of time"), 0);
15614             }
15615             break;
15616         }
15617     }
15618 }
15619
15620 void
15621 ClockClick (int which)
15622 {       // [HGM] code moved to back-end from winboard.c
15623         if(which) { // black clock
15624           if (gameMode == EditPosition || gameMode == IcsExamining) {
15625             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15626             SetBlackToPlayEvent();
15627           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15628                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15629           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15630           } else if (shiftKey) {
15631             AdjustClock(which, -1);
15632           } else if (gameMode == IcsPlayingWhite ||
15633                      gameMode == MachinePlaysBlack) {
15634             CallFlagEvent();
15635           }
15636         } else { // white clock
15637           if (gameMode == EditPosition || gameMode == IcsExamining) {
15638             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15639             SetWhiteToPlayEvent();
15640           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15641                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15642           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15643           } else if (shiftKey) {
15644             AdjustClock(which, -1);
15645           } else if (gameMode == IcsPlayingBlack ||
15646                    gameMode == MachinePlaysWhite) {
15647             CallFlagEvent();
15648           }
15649         }
15650 }
15651
15652 void
15653 DrawEvent ()
15654 {
15655     /* Offer draw or accept pending draw offer from opponent */
15656
15657     if (appData.icsActive) {
15658         /* Note: tournament rules require draw offers to be
15659            made after you make your move but before you punch
15660            your clock.  Currently ICS doesn't let you do that;
15661            instead, you immediately punch your clock after making
15662            a move, but you can offer a draw at any time. */
15663
15664         SendToICS(ics_prefix);
15665         SendToICS("draw\n");
15666         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15667     } else if (cmailMsgLoaded) {
15668         if (currentMove == cmailOldMove &&
15669             commentList[cmailOldMove] != NULL &&
15670             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15671                    "Black offers a draw" : "White offers a draw")) {
15672             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15673             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15674         } else if (currentMove == cmailOldMove + 1) {
15675             char *offer = WhiteOnMove(cmailOldMove) ?
15676               "White offers a draw" : "Black offers a draw";
15677             AppendComment(currentMove, offer, TRUE);
15678             DisplayComment(currentMove - 1, offer);
15679             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15680         } else {
15681             DisplayError(_("You must make your move before offering a draw"), 0);
15682             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15683         }
15684     } else if (first.offeredDraw) {
15685         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15686     } else {
15687         if (first.sendDrawOffers) {
15688             SendToProgram("draw\n", &first);
15689             userOfferedDraw = TRUE;
15690         }
15691     }
15692 }
15693
15694 void
15695 AdjournEvent ()
15696 {
15697     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15698
15699     if (appData.icsActive) {
15700         SendToICS(ics_prefix);
15701         SendToICS("adjourn\n");
15702     } else {
15703         /* Currently GNU Chess doesn't offer or accept Adjourns */
15704     }
15705 }
15706
15707
15708 void
15709 AbortEvent ()
15710 {
15711     /* Offer Abort or accept pending Abort offer from opponent */
15712
15713     if (appData.icsActive) {
15714         SendToICS(ics_prefix);
15715         SendToICS("abort\n");
15716     } else {
15717         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15718     }
15719 }
15720
15721 void
15722 ResignEvent ()
15723 {
15724     /* Resign.  You can do this even if it's not your turn. */
15725
15726     if (appData.icsActive) {
15727         SendToICS(ics_prefix);
15728         SendToICS("resign\n");
15729     } else {
15730         switch (gameMode) {
15731           case MachinePlaysWhite:
15732             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15733             break;
15734           case MachinePlaysBlack:
15735             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15736             break;
15737           case EditGame:
15738             if (cmailMsgLoaded) {
15739                 TruncateGame();
15740                 if (WhiteOnMove(cmailOldMove)) {
15741                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15742                 } else {
15743                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15744                 }
15745                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15746             }
15747             break;
15748           default:
15749             break;
15750         }
15751     }
15752 }
15753
15754
15755 void
15756 StopObservingEvent ()
15757 {
15758     /* Stop observing current games */
15759     SendToICS(ics_prefix);
15760     SendToICS("unobserve\n");
15761 }
15762
15763 void
15764 StopExaminingEvent ()
15765 {
15766     /* Stop observing current game */
15767     SendToICS(ics_prefix);
15768     SendToICS("unexamine\n");
15769 }
15770
15771 void
15772 ForwardInner (int target)
15773 {
15774     int limit; int oldSeekGraphUp = seekGraphUp;
15775
15776     if (appData.debugMode)
15777         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15778                 target, currentMove, forwardMostMove);
15779
15780     if (gameMode == EditPosition)
15781       return;
15782
15783     seekGraphUp = FALSE;
15784     MarkTargetSquares(1);
15785     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15786
15787     if (gameMode == PlayFromGameFile && !pausing)
15788       PauseEvent();
15789
15790     if (gameMode == IcsExamining && pausing)
15791       limit = pauseExamForwardMostMove;
15792     else
15793       limit = forwardMostMove;
15794
15795     if (target > limit) target = limit;
15796
15797     if (target > 0 && moveList[target - 1][0]) {
15798         int fromX, fromY, toX, toY;
15799         toX = moveList[target - 1][2] - AAA;
15800         toY = moveList[target - 1][3] - ONE;
15801         if (moveList[target - 1][1] == '@') {
15802             if (appData.highlightLastMove) {
15803                 SetHighlights(-1, -1, toX, toY);
15804             }
15805         } else {
15806             int viaX = moveList[target - 1][5] - AAA;
15807             int viaY = moveList[target - 1][6] - ONE;
15808             fromX = moveList[target - 1][0] - AAA;
15809             fromY = moveList[target - 1][1] - ONE;
15810             if (target == currentMove + 1) {
15811                 if(moveList[target - 1][4] == ';') { // multi-leg
15812                     ChessSquare piece = boards[currentMove][viaY][viaX];
15813                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15814                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15815                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15816                     boards[currentMove][viaY][viaX] = piece;
15817                 } else
15818                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15819             }
15820             if (appData.highlightLastMove) {
15821                 SetHighlights(fromX, fromY, toX, toY);
15822             }
15823         }
15824     }
15825     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15826         gameMode == Training || gameMode == PlayFromGameFile ||
15827         gameMode == AnalyzeFile) {
15828         while (currentMove < target) {
15829             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15830             SendMoveToProgram(currentMove++, &first);
15831         }
15832     } else {
15833         currentMove = target;
15834     }
15835
15836     if (gameMode == EditGame || gameMode == EndOfGame) {
15837         whiteTimeRemaining = timeRemaining[0][currentMove];
15838         blackTimeRemaining = timeRemaining[1][currentMove];
15839     }
15840     DisplayBothClocks();
15841     DisplayMove(currentMove - 1);
15842     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15843     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15844     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15845         DisplayComment(currentMove - 1, commentList[currentMove]);
15846     }
15847     ClearMap(); // [HGM] exclude: invalidate map
15848 }
15849
15850
15851 void
15852 ForwardEvent ()
15853 {
15854     if (gameMode == IcsExamining && !pausing) {
15855         SendToICS(ics_prefix);
15856         SendToICS("forward\n");
15857     } else {
15858         ForwardInner(currentMove + 1);
15859     }
15860 }
15861
15862 void
15863 ToEndEvent ()
15864 {
15865     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15866         /* to optimze, we temporarily turn off analysis mode while we feed
15867          * the remaining moves to the engine. Otherwise we get analysis output
15868          * after each move.
15869          */
15870         if (first.analysisSupport) {
15871           SendToProgram("exit\nforce\n", &first);
15872           first.analyzing = FALSE;
15873         }
15874     }
15875
15876     if (gameMode == IcsExamining && !pausing) {
15877         SendToICS(ics_prefix);
15878         SendToICS("forward 999999\n");
15879     } else {
15880         ForwardInner(forwardMostMove);
15881     }
15882
15883     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15884         /* we have fed all the moves, so reactivate analysis mode */
15885         SendToProgram("analyze\n", &first);
15886         first.analyzing = TRUE;
15887         /*first.maybeThinking = TRUE;*/
15888         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15889     }
15890 }
15891
15892 void
15893 BackwardInner (int target)
15894 {
15895     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15896
15897     if (appData.debugMode)
15898         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15899                 target, currentMove, forwardMostMove);
15900
15901     if (gameMode == EditPosition) return;
15902     seekGraphUp = FALSE;
15903     MarkTargetSquares(1);
15904     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15905     if (currentMove <= backwardMostMove) {
15906         ClearHighlights();
15907         DrawPosition(full_redraw, boards[currentMove]);
15908         return;
15909     }
15910     if (gameMode == PlayFromGameFile && !pausing)
15911       PauseEvent();
15912
15913     if (moveList[target][0]) {
15914         int fromX, fromY, toX, toY;
15915         toX = moveList[target][2] - AAA;
15916         toY = moveList[target][3] - ONE;
15917         if (moveList[target][1] == '@') {
15918             if (appData.highlightLastMove) {
15919                 SetHighlights(-1, -1, toX, toY);
15920             }
15921         } else {
15922             fromX = moveList[target][0] - AAA;
15923             fromY = moveList[target][1] - ONE;
15924             if (target == currentMove - 1) {
15925                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15926             }
15927             if (appData.highlightLastMove) {
15928                 SetHighlights(fromX, fromY, toX, toY);
15929             }
15930         }
15931     }
15932     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15933         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15934         while (currentMove > target) {
15935             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15936                 // null move cannot be undone. Reload program with move history before it.
15937                 int i;
15938                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15939                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15940                 }
15941                 SendBoard(&first, i);
15942               if(second.analyzing) SendBoard(&second, i);
15943                 for(currentMove=i; currentMove<target; currentMove++) {
15944                     SendMoveToProgram(currentMove, &first);
15945                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15946                 }
15947                 break;
15948             }
15949             SendToBoth("undo\n");
15950             currentMove--;
15951         }
15952     } else {
15953         currentMove = target;
15954     }
15955
15956     if (gameMode == EditGame || gameMode == EndOfGame) {
15957         whiteTimeRemaining = timeRemaining[0][currentMove];
15958         blackTimeRemaining = timeRemaining[1][currentMove];
15959     }
15960     DisplayBothClocks();
15961     DisplayMove(currentMove - 1);
15962     DrawPosition(full_redraw, boards[currentMove]);
15963     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15964     // [HGM] PV info: routine tests if comment empty
15965     DisplayComment(currentMove - 1, commentList[currentMove]);
15966     ClearMap(); // [HGM] exclude: invalidate map
15967 }
15968
15969 void
15970 BackwardEvent ()
15971 {
15972     if (gameMode == IcsExamining && !pausing) {
15973         SendToICS(ics_prefix);
15974         SendToICS("backward\n");
15975     } else {
15976         BackwardInner(currentMove - 1);
15977     }
15978 }
15979
15980 void
15981 ToStartEvent ()
15982 {
15983     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15984         /* to optimize, we temporarily turn off analysis mode while we undo
15985          * all the moves. Otherwise we get analysis output after each undo.
15986          */
15987         if (first.analysisSupport) {
15988           SendToProgram("exit\nforce\n", &first);
15989           first.analyzing = FALSE;
15990         }
15991     }
15992
15993     if (gameMode == IcsExamining && !pausing) {
15994         SendToICS(ics_prefix);
15995         SendToICS("backward 999999\n");
15996     } else {
15997         BackwardInner(backwardMostMove);
15998     }
15999
16000     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16001         /* we have fed all the moves, so reactivate analysis mode */
16002         SendToProgram("analyze\n", &first);
16003         first.analyzing = TRUE;
16004         /*first.maybeThinking = TRUE;*/
16005         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16006     }
16007 }
16008
16009 void
16010 ToNrEvent (int to)
16011 {
16012   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16013   if (to >= forwardMostMove) to = forwardMostMove;
16014   if (to <= backwardMostMove) to = backwardMostMove;
16015   if (to < currentMove) {
16016     BackwardInner(to);
16017   } else {
16018     ForwardInner(to);
16019   }
16020 }
16021
16022 void
16023 RevertEvent (Boolean annotate)
16024 {
16025     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16026         return;
16027     }
16028     if (gameMode != IcsExamining) {
16029         DisplayError(_("You are not examining a game"), 0);
16030         return;
16031     }
16032     if (pausing) {
16033         DisplayError(_("You can't revert while pausing"), 0);
16034         return;
16035     }
16036     SendToICS(ics_prefix);
16037     SendToICS("revert\n");
16038 }
16039
16040 void
16041 RetractMoveEvent ()
16042 {
16043     switch (gameMode) {
16044       case MachinePlaysWhite:
16045       case MachinePlaysBlack:
16046         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16047             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16048             return;
16049         }
16050         if (forwardMostMove < 2) return;
16051         currentMove = forwardMostMove = forwardMostMove - 2;
16052         whiteTimeRemaining = timeRemaining[0][currentMove];
16053         blackTimeRemaining = timeRemaining[1][currentMove];
16054         DisplayBothClocks();
16055         DisplayMove(currentMove - 1);
16056         ClearHighlights();/*!! could figure this out*/
16057         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16058         SendToProgram("remove\n", &first);
16059         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16060         break;
16061
16062       case BeginningOfGame:
16063       default:
16064         break;
16065
16066       case IcsPlayingWhite:
16067       case IcsPlayingBlack:
16068         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16069             SendToICS(ics_prefix);
16070             SendToICS("takeback 2\n");
16071         } else {
16072             SendToICS(ics_prefix);
16073             SendToICS("takeback 1\n");
16074         }
16075         break;
16076     }
16077 }
16078
16079 void
16080 MoveNowEvent ()
16081 {
16082     ChessProgramState *cps;
16083
16084     switch (gameMode) {
16085       case MachinePlaysWhite:
16086         if (!WhiteOnMove(forwardMostMove)) {
16087             DisplayError(_("It is your turn"), 0);
16088             return;
16089         }
16090         cps = &first;
16091         break;
16092       case MachinePlaysBlack:
16093         if (WhiteOnMove(forwardMostMove)) {
16094             DisplayError(_("It is your turn"), 0);
16095             return;
16096         }
16097         cps = &first;
16098         break;
16099       case TwoMachinesPlay:
16100         if (WhiteOnMove(forwardMostMove) ==
16101             (first.twoMachinesColor[0] == 'w')) {
16102             cps = &first;
16103         } else {
16104             cps = &second;
16105         }
16106         break;
16107       case BeginningOfGame:
16108       default:
16109         return;
16110     }
16111     SendToProgram("?\n", cps);
16112 }
16113
16114 void
16115 TruncateGameEvent ()
16116 {
16117     EditGameEvent();
16118     if (gameMode != EditGame) return;
16119     TruncateGame();
16120 }
16121
16122 void
16123 TruncateGame ()
16124 {
16125     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16126     if (forwardMostMove > currentMove) {
16127         if (gameInfo.resultDetails != NULL) {
16128             free(gameInfo.resultDetails);
16129             gameInfo.resultDetails = NULL;
16130             gameInfo.result = GameUnfinished;
16131         }
16132         forwardMostMove = currentMove;
16133         HistorySet(parseList, backwardMostMove, forwardMostMove,
16134                    currentMove-1);
16135     }
16136 }
16137
16138 void
16139 HintEvent ()
16140 {
16141     if (appData.noChessProgram) return;
16142     switch (gameMode) {
16143       case MachinePlaysWhite:
16144         if (WhiteOnMove(forwardMostMove)) {
16145             DisplayError(_("Wait until your turn."), 0);
16146             return;
16147         }
16148         break;
16149       case BeginningOfGame:
16150       case MachinePlaysBlack:
16151         if (!WhiteOnMove(forwardMostMove)) {
16152             DisplayError(_("Wait until your turn."), 0);
16153             return;
16154         }
16155         break;
16156       default:
16157         DisplayError(_("No hint available"), 0);
16158         return;
16159     }
16160     SendToProgram("hint\n", &first);
16161     hintRequested = TRUE;
16162 }
16163
16164 int
16165 SaveSelected (FILE *g, int dummy, char *dummy2)
16166 {
16167     ListGame * lg = (ListGame *) gameList.head;
16168     int nItem, cnt=0;
16169     FILE *f;
16170
16171     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16172         DisplayError(_("Game list not loaded or empty"), 0);
16173         return 0;
16174     }
16175
16176     creatingBook = TRUE; // suppresses stuff during load game
16177
16178     /* Get list size */
16179     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16180         if(lg->position >= 0) { // selected?
16181             LoadGame(f, nItem, "", TRUE);
16182             SaveGamePGN2(g); // leaves g open
16183             cnt++; DoEvents();
16184         }
16185         lg = (ListGame *) lg->node.succ;
16186     }
16187
16188     fclose(g);
16189     creatingBook = FALSE;
16190
16191     return cnt;
16192 }
16193
16194 void
16195 CreateBookEvent ()
16196 {
16197     ListGame * lg = (ListGame *) gameList.head;
16198     FILE *f, *g;
16199     int nItem;
16200     static int secondTime = FALSE;
16201
16202     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16203         DisplayError(_("Game list not loaded or empty"), 0);
16204         return;
16205     }
16206
16207     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16208         fclose(g);
16209         secondTime++;
16210         DisplayNote(_("Book file exists! Try again for overwrite."));
16211         return;
16212     }
16213
16214     creatingBook = TRUE;
16215     secondTime = FALSE;
16216
16217     /* Get list size */
16218     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16219         if(lg->position >= 0) {
16220             LoadGame(f, nItem, "", TRUE);
16221             AddGameToBook(TRUE);
16222             DoEvents();
16223         }
16224         lg = (ListGame *) lg->node.succ;
16225     }
16226
16227     creatingBook = FALSE;
16228     FlushBook();
16229 }
16230
16231 void
16232 BookEvent ()
16233 {
16234     if (appData.noChessProgram) return;
16235     switch (gameMode) {
16236       case MachinePlaysWhite:
16237         if (WhiteOnMove(forwardMostMove)) {
16238             DisplayError(_("Wait until your turn."), 0);
16239             return;
16240         }
16241         break;
16242       case BeginningOfGame:
16243       case MachinePlaysBlack:
16244         if (!WhiteOnMove(forwardMostMove)) {
16245             DisplayError(_("Wait until your turn."), 0);
16246             return;
16247         }
16248         break;
16249       case EditPosition:
16250         EditPositionDone(TRUE);
16251         break;
16252       case TwoMachinesPlay:
16253         return;
16254       default:
16255         break;
16256     }
16257     SendToProgram("bk\n", &first);
16258     bookOutput[0] = NULLCHAR;
16259     bookRequested = TRUE;
16260 }
16261
16262 void
16263 AboutGameEvent ()
16264 {
16265     char *tags = PGNTags(&gameInfo);
16266     TagsPopUp(tags, CmailMsg());
16267     free(tags);
16268 }
16269
16270 /* end button procedures */
16271
16272 void
16273 PrintPosition (FILE *fp, int move)
16274 {
16275     int i, j;
16276
16277     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16278         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16279             char c = PieceToChar(boards[move][i][j]);
16280             fputc(c == 'x' ? '.' : c, fp);
16281             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16282         }
16283     }
16284     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16285       fprintf(fp, "white to play\n");
16286     else
16287       fprintf(fp, "black to play\n");
16288 }
16289
16290 void
16291 PrintOpponents (FILE *fp)
16292 {
16293     if (gameInfo.white != NULL) {
16294         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16295     } else {
16296         fprintf(fp, "\n");
16297     }
16298 }
16299
16300 /* Find last component of program's own name, using some heuristics */
16301 void
16302 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16303 {
16304     char *p, *q, c;
16305     int local = (strcmp(host, "localhost") == 0);
16306     while (!local && (p = strchr(prog, ';')) != NULL) {
16307         p++;
16308         while (*p == ' ') p++;
16309         prog = p;
16310     }
16311     if (*prog == '"' || *prog == '\'') {
16312         q = strchr(prog + 1, *prog);
16313     } else {
16314         q = strchr(prog, ' ');
16315     }
16316     if (q == NULL) q = prog + strlen(prog);
16317     p = q;
16318     while (p >= prog && *p != '/' && *p != '\\') p--;
16319     p++;
16320     if(p == prog && *p == '"') p++;
16321     c = *q; *q = 0;
16322     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16323     memcpy(buf, p, q - p);
16324     buf[q - p] = NULLCHAR;
16325     if (!local) {
16326         strcat(buf, "@");
16327         strcat(buf, host);
16328     }
16329 }
16330
16331 char *
16332 TimeControlTagValue ()
16333 {
16334     char buf[MSG_SIZ];
16335     if (!appData.clockMode) {
16336       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16337     } else if (movesPerSession > 0) {
16338       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16339     } else if (timeIncrement == 0) {
16340       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16341     } else {
16342       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16343     }
16344     return StrSave(buf);
16345 }
16346
16347 void
16348 SetGameInfo ()
16349 {
16350     /* This routine is used only for certain modes */
16351     VariantClass v = gameInfo.variant;
16352     ChessMove r = GameUnfinished;
16353     char *p = NULL;
16354
16355     if(keepInfo) return;
16356
16357     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16358         r = gameInfo.result;
16359         p = gameInfo.resultDetails;
16360         gameInfo.resultDetails = NULL;
16361     }
16362     ClearGameInfo(&gameInfo);
16363     gameInfo.variant = v;
16364
16365     switch (gameMode) {
16366       case MachinePlaysWhite:
16367         gameInfo.event = StrSave( appData.pgnEventHeader );
16368         gameInfo.site = StrSave(HostName());
16369         gameInfo.date = PGNDate();
16370         gameInfo.round = StrSave("-");
16371         gameInfo.white = StrSave(first.tidy);
16372         gameInfo.black = StrSave(UserName());
16373         gameInfo.timeControl = TimeControlTagValue();
16374         break;
16375
16376       case MachinePlaysBlack:
16377         gameInfo.event = StrSave( appData.pgnEventHeader );
16378         gameInfo.site = StrSave(HostName());
16379         gameInfo.date = PGNDate();
16380         gameInfo.round = StrSave("-");
16381         gameInfo.white = StrSave(UserName());
16382         gameInfo.black = StrSave(first.tidy);
16383         gameInfo.timeControl = TimeControlTagValue();
16384         break;
16385
16386       case TwoMachinesPlay:
16387         gameInfo.event = StrSave( appData.pgnEventHeader );
16388         gameInfo.site = StrSave(HostName());
16389         gameInfo.date = PGNDate();
16390         if (roundNr > 0) {
16391             char buf[MSG_SIZ];
16392             snprintf(buf, MSG_SIZ, "%d", roundNr);
16393             gameInfo.round = StrSave(buf);
16394         } else {
16395             gameInfo.round = StrSave("-");
16396         }
16397         if (first.twoMachinesColor[0] == 'w') {
16398             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16399             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16400         } else {
16401             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16402             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16403         }
16404         gameInfo.timeControl = TimeControlTagValue();
16405         break;
16406
16407       case EditGame:
16408         gameInfo.event = StrSave("Edited game");
16409         gameInfo.site = StrSave(HostName());
16410         gameInfo.date = PGNDate();
16411         gameInfo.round = StrSave("-");
16412         gameInfo.white = StrSave("-");
16413         gameInfo.black = StrSave("-");
16414         gameInfo.result = r;
16415         gameInfo.resultDetails = p;
16416         break;
16417
16418       case EditPosition:
16419         gameInfo.event = StrSave("Edited position");
16420         gameInfo.site = StrSave(HostName());
16421         gameInfo.date = PGNDate();
16422         gameInfo.round = StrSave("-");
16423         gameInfo.white = StrSave("-");
16424         gameInfo.black = StrSave("-");
16425         break;
16426
16427       case IcsPlayingWhite:
16428       case IcsPlayingBlack:
16429       case IcsObserving:
16430       case IcsExamining:
16431         break;
16432
16433       case PlayFromGameFile:
16434         gameInfo.event = StrSave("Game from non-PGN file");
16435         gameInfo.site = StrSave(HostName());
16436         gameInfo.date = PGNDate();
16437         gameInfo.round = StrSave("-");
16438         gameInfo.white = StrSave("?");
16439         gameInfo.black = StrSave("?");
16440         break;
16441
16442       default:
16443         break;
16444     }
16445 }
16446
16447 void
16448 ReplaceComment (int index, char *text)
16449 {
16450     int len;
16451     char *p;
16452     float score;
16453
16454     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16455        pvInfoList[index-1].depth == len &&
16456        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16457        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16458     while (*text == '\n') text++;
16459     len = strlen(text);
16460     while (len > 0 && text[len - 1] == '\n') len--;
16461
16462     if (commentList[index] != NULL)
16463       free(commentList[index]);
16464
16465     if (len == 0) {
16466         commentList[index] = NULL;
16467         return;
16468     }
16469   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16470       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16471       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16472     commentList[index] = (char *) malloc(len + 2);
16473     strncpy(commentList[index], text, len);
16474     commentList[index][len] = '\n';
16475     commentList[index][len + 1] = NULLCHAR;
16476   } else {
16477     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16478     char *p;
16479     commentList[index] = (char *) malloc(len + 7);
16480     safeStrCpy(commentList[index], "{\n", 3);
16481     safeStrCpy(commentList[index]+2, text, len+1);
16482     commentList[index][len+2] = NULLCHAR;
16483     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16484     strcat(commentList[index], "\n}\n");
16485   }
16486 }
16487
16488 void
16489 CrushCRs (char *text)
16490 {
16491   char *p = text;
16492   char *q = text;
16493   char ch;
16494
16495   do {
16496     ch = *p++;
16497     if (ch == '\r') continue;
16498     *q++ = ch;
16499   } while (ch != '\0');
16500 }
16501
16502 void
16503 AppendComment (int index, char *text, Boolean addBraces)
16504 /* addBraces  tells if we should add {} */
16505 {
16506     int oldlen, len;
16507     char *old;
16508
16509 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16510     if(addBraces == 3) addBraces = 0; else // force appending literally
16511     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16512
16513     CrushCRs(text);
16514     while (*text == '\n') text++;
16515     len = strlen(text);
16516     while (len > 0 && text[len - 1] == '\n') len--;
16517     text[len] = NULLCHAR;
16518
16519     if (len == 0) return;
16520
16521     if (commentList[index] != NULL) {
16522       Boolean addClosingBrace = addBraces;
16523         old = commentList[index];
16524         oldlen = strlen(old);
16525         while(commentList[index][oldlen-1] ==  '\n')
16526           commentList[index][--oldlen] = NULLCHAR;
16527         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16528         safeStrCpy(commentList[index], old, oldlen + len + 6);
16529         free(old);
16530         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16531         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16532           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16533           while (*text == '\n') { text++; len--; }
16534           commentList[index][--oldlen] = NULLCHAR;
16535       }
16536         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16537         else          strcat(commentList[index], "\n");
16538         strcat(commentList[index], text);
16539         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16540         else          strcat(commentList[index], "\n");
16541     } else {
16542         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16543         if(addBraces)
16544           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16545         else commentList[index][0] = NULLCHAR;
16546         strcat(commentList[index], text);
16547         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16548         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16549     }
16550 }
16551
16552 static char *
16553 FindStr (char * text, char * sub_text)
16554 {
16555     char * result = strstr( text, sub_text );
16556
16557     if( result != NULL ) {
16558         result += strlen( sub_text );
16559     }
16560
16561     return result;
16562 }
16563
16564 /* [AS] Try to extract PV info from PGN comment */
16565 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16566 char *
16567 GetInfoFromComment (int index, char * text)
16568 {
16569     char * sep = text, *p;
16570
16571     if( text != NULL && index > 0 ) {
16572         int score = 0;
16573         int depth = 0;
16574         int time = -1, sec = 0, deci;
16575         char * s_eval = FindStr( text, "[%eval " );
16576         char * s_emt = FindStr( text, "[%emt " );
16577 #if 0
16578         if( s_eval != NULL || s_emt != NULL ) {
16579 #else
16580         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16581 #endif
16582             /* New style */
16583             char delim;
16584
16585             if( s_eval != NULL ) {
16586                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16587                     return text;
16588                 }
16589
16590                 if( delim != ']' ) {
16591                     return text;
16592                 }
16593             }
16594
16595             if( s_emt != NULL ) {
16596             }
16597                 return text;
16598         }
16599         else {
16600             /* We expect something like: [+|-]nnn.nn/dd */
16601             int score_lo = 0;
16602
16603             if(*text != '{') return text; // [HGM] braces: must be normal comment
16604
16605             sep = strchr( text, '/' );
16606             if( sep == NULL || sep < (text+4) ) {
16607                 return text;
16608             }
16609
16610             p = text;
16611             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16612             if(p[1] == '(') { // comment starts with PV
16613                p = strchr(p, ')'); // locate end of PV
16614                if(p == NULL || sep < p+5) return text;
16615                // at this point we have something like "{(.*) +0.23/6 ..."
16616                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16617                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16618                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16619             }
16620             time = -1; sec = -1; deci = -1;
16621             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16622                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16623                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16624                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16625                 return text;
16626             }
16627
16628             if( score_lo < 0 || score_lo >= 100 ) {
16629                 return text;
16630             }
16631
16632             if(sec >= 0) time = 600*time + 10*sec; else
16633             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16634
16635             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16636
16637             /* [HGM] PV time: now locate end of PV info */
16638             while( *++sep >= '0' && *sep <= '9'); // strip depth
16639             if(time >= 0)
16640             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16641             if(sec >= 0)
16642             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16643             if(deci >= 0)
16644             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16645             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16646         }
16647
16648         if( depth <= 0 ) {
16649             return text;
16650         }
16651
16652         if( time < 0 ) {
16653             time = -1;
16654         }
16655
16656         pvInfoList[index-1].depth = depth;
16657         pvInfoList[index-1].score = score;
16658         pvInfoList[index-1].time  = 10*time; // centi-sec
16659         if(*sep == '}') *sep = 0; else *--sep = '{';
16660         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16661     }
16662     return sep;
16663 }
16664
16665 void
16666 SendToProgram (char *message, ChessProgramState *cps)
16667 {
16668     int count, outCount, error;
16669     char buf[MSG_SIZ];
16670
16671     if (cps->pr == NoProc) return;
16672     Attention(cps);
16673
16674     if (appData.debugMode) {
16675         TimeMark now;
16676         GetTimeMark(&now);
16677         fprintf(debugFP, "%ld >%-6s: %s",
16678                 SubtractTimeMarks(&now, &programStartTime),
16679                 cps->which, message);
16680         if(serverFP)
16681             fprintf(serverFP, "%ld >%-6s: %s",
16682                 SubtractTimeMarks(&now, &programStartTime),
16683                 cps->which, message), fflush(serverFP);
16684     }
16685
16686     count = strlen(message);
16687     outCount = OutputToProcess(cps->pr, message, count, &error);
16688     if (outCount < count && !exiting
16689                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16690       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16691       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16692         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16693             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16694                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16695                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16696                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16697             } else {
16698                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16699                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16700                 gameInfo.result = res;
16701             }
16702             gameInfo.resultDetails = StrSave(buf);
16703         }
16704         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16705         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16706     }
16707 }
16708
16709 void
16710 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16711 {
16712     char *end_str;
16713     char buf[MSG_SIZ];
16714     ChessProgramState *cps = (ChessProgramState *)closure;
16715
16716     if (isr != cps->isr) return; /* Killed intentionally */
16717     if (count <= 0) {
16718         if (count == 0) {
16719             RemoveInputSource(cps->isr);
16720             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16721                     _(cps->which), cps->program);
16722             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16723             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16724                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16725                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16726                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16727                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16728                 } else {
16729                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16730                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16731                     gameInfo.result = res;
16732                 }
16733                 gameInfo.resultDetails = StrSave(buf);
16734             }
16735             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16736             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16737         } else {
16738             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16739                     _(cps->which), cps->program);
16740             RemoveInputSource(cps->isr);
16741
16742             /* [AS] Program is misbehaving badly... kill it */
16743             if( count == -2 ) {
16744                 DestroyChildProcess( cps->pr, 9 );
16745                 cps->pr = NoProc;
16746             }
16747
16748             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16749         }
16750         return;
16751     }
16752
16753     if ((end_str = strchr(message, '\r')) != NULL)
16754       *end_str = NULLCHAR;
16755     if ((end_str = strchr(message, '\n')) != NULL)
16756       *end_str = NULLCHAR;
16757
16758     if (appData.debugMode) {
16759         TimeMark now; int print = 1;
16760         char *quote = ""; char c; int i;
16761
16762         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16763                 char start = message[0];
16764                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16765                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16766                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16767                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16768                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16769                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16770                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16771                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16772                    sscanf(message, "hint: %c", &c)!=1 &&
16773                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16774                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16775                     print = (appData.engineComments >= 2);
16776                 }
16777                 message[0] = start; // restore original message
16778         }
16779         if(print) {
16780                 GetTimeMark(&now);
16781                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16782                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16783                         quote,
16784                         message);
16785                 if(serverFP)
16786                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16787                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16788                         quote,
16789                         message), fflush(serverFP);
16790         }
16791     }
16792
16793     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16794     if (appData.icsEngineAnalyze) {
16795         if (strstr(message, "whisper") != NULL ||
16796              strstr(message, "kibitz") != NULL ||
16797             strstr(message, "tellics") != NULL) return;
16798     }
16799
16800     HandleMachineMove(message, cps);
16801 }
16802
16803
16804 void
16805 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16806 {
16807     char buf[MSG_SIZ];
16808     int seconds;
16809
16810     if( timeControl_2 > 0 ) {
16811         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16812             tc = timeControl_2;
16813         }
16814     }
16815     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16816     inc /= cps->timeOdds;
16817     st  /= cps->timeOdds;
16818
16819     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16820
16821     if (st > 0) {
16822       /* Set exact time per move, normally using st command */
16823       if (cps->stKludge) {
16824         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16825         seconds = st % 60;
16826         if (seconds == 0) {
16827           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16828         } else {
16829           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16830         }
16831       } else {
16832         snprintf(buf, MSG_SIZ, "st %d\n", st);
16833       }
16834     } else {
16835       /* Set conventional or incremental time control, using level command */
16836       if (seconds == 0) {
16837         /* Note old gnuchess bug -- minutes:seconds used to not work.
16838            Fixed in later versions, but still avoid :seconds
16839            when seconds is 0. */
16840         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16841       } else {
16842         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16843                  seconds, inc/1000.);
16844       }
16845     }
16846     SendToProgram(buf, cps);
16847
16848     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16849     /* Orthogonally, limit search to given depth */
16850     if (sd > 0) {
16851       if (cps->sdKludge) {
16852         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16853       } else {
16854         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16855       }
16856       SendToProgram(buf, cps);
16857     }
16858
16859     if(cps->nps >= 0) { /* [HGM] nps */
16860         if(cps->supportsNPS == FALSE)
16861           cps->nps = -1; // don't use if engine explicitly says not supported!
16862         else {
16863           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16864           SendToProgram(buf, cps);
16865         }
16866     }
16867 }
16868
16869 ChessProgramState *
16870 WhitePlayer ()
16871 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16872 {
16873     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16874        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16875         return &second;
16876     return &first;
16877 }
16878
16879 void
16880 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16881 {
16882     char message[MSG_SIZ];
16883     long time, otime;
16884
16885     /* Note: this routine must be called when the clocks are stopped
16886        or when they have *just* been set or switched; otherwise
16887        it will be off by the time since the current tick started.
16888     */
16889     if (machineWhite) {
16890         time = whiteTimeRemaining / 10;
16891         otime = blackTimeRemaining / 10;
16892     } else {
16893         time = blackTimeRemaining / 10;
16894         otime = whiteTimeRemaining / 10;
16895     }
16896     /* [HGM] translate opponent's time by time-odds factor */
16897     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16898
16899     if (time <= 0) time = 1;
16900     if (otime <= 0) otime = 1;
16901
16902     snprintf(message, MSG_SIZ, "time %ld\n", time);
16903     SendToProgram(message, cps);
16904
16905     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16906     SendToProgram(message, cps);
16907 }
16908
16909 char *
16910 EngineDefinedVariant (ChessProgramState *cps, int n)
16911 {   // return name of n-th unknown variant that engine supports
16912     static char buf[MSG_SIZ];
16913     char *p, *s = cps->variants;
16914     if(!s) return NULL;
16915     do { // parse string from variants feature
16916       VariantClass v;
16917         p = strchr(s, ',');
16918         if(p) *p = NULLCHAR;
16919       v = StringToVariant(s);
16920       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16921         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16922             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16923                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16924                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16925                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16926             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16927         }
16928         if(p) *p++ = ',';
16929         if(n < 0) return buf;
16930     } while(s = p);
16931     return NULL;
16932 }
16933
16934 int
16935 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16936 {
16937   char buf[MSG_SIZ];
16938   int len = strlen(name);
16939   int val;
16940
16941   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16942     (*p) += len + 1;
16943     sscanf(*p, "%d", &val);
16944     *loc = (val != 0);
16945     while (**p && **p != ' ')
16946       (*p)++;
16947     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16948     SendToProgram(buf, cps);
16949     return TRUE;
16950   }
16951   return FALSE;
16952 }
16953
16954 int
16955 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16956 {
16957   char buf[MSG_SIZ];
16958   int len = strlen(name);
16959   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16960     (*p) += len + 1;
16961     sscanf(*p, "%d", loc);
16962     while (**p && **p != ' ') (*p)++;
16963     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16964     SendToProgram(buf, cps);
16965     return TRUE;
16966   }
16967   return FALSE;
16968 }
16969
16970 int
16971 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16972 {
16973   char buf[MSG_SIZ];
16974   int len = strlen(name);
16975   if (strncmp((*p), name, len) == 0
16976       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16977     (*p) += len + 2;
16978     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16979     sscanf(*p, "%[^\"]", *loc);
16980     while (**p && **p != '\"') (*p)++;
16981     if (**p == '\"') (*p)++;
16982     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16983     SendToProgram(buf, cps);
16984     return TRUE;
16985   }
16986   return FALSE;
16987 }
16988
16989 int
16990 ParseOption (Option *opt, ChessProgramState *cps)
16991 // [HGM] options: process the string that defines an engine option, and determine
16992 // name, type, default value, and allowed value range
16993 {
16994         char *p, *q, buf[MSG_SIZ];
16995         int n, min = (-1)<<31, max = 1<<31, def;
16996
16997         opt->target = &opt->value;   // OK for spin/slider and checkbox
16998         if(p = strstr(opt->name, " -spin ")) {
16999             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17000             if(max < min) max = min; // enforce consistency
17001             if(def < min) def = min;
17002             if(def > max) def = max;
17003             opt->value = def;
17004             opt->min = min;
17005             opt->max = max;
17006             opt->type = Spin;
17007         } else if((p = strstr(opt->name, " -slider "))) {
17008             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17009             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17010             if(max < min) max = min; // enforce consistency
17011             if(def < min) def = min;
17012             if(def > max) def = max;
17013             opt->value = def;
17014             opt->min = min;
17015             opt->max = max;
17016             opt->type = Spin; // Slider;
17017         } else if((p = strstr(opt->name, " -string "))) {
17018             opt->textValue = p+9;
17019             opt->type = TextBox;
17020             opt->target = &opt->textValue;
17021         } else if((p = strstr(opt->name, " -file "))) {
17022             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17023             opt->target = opt->textValue = p+7;
17024             opt->type = FileName; // FileName;
17025             opt->target = &opt->textValue;
17026         } else if((p = strstr(opt->name, " -path "))) {
17027             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17028             opt->target = opt->textValue = p+7;
17029             opt->type = PathName; // PathName;
17030             opt->target = &opt->textValue;
17031         } else if(p = strstr(opt->name, " -check ")) {
17032             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17033             opt->value = (def != 0);
17034             opt->type = CheckBox;
17035         } else if(p = strstr(opt->name, " -combo ")) {
17036             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17037             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17038             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17039             opt->value = n = 0;
17040             while(q = StrStr(q, " /// ")) {
17041                 n++; *q = 0;    // count choices, and null-terminate each of them
17042                 q += 5;
17043                 if(*q == '*') { // remember default, which is marked with * prefix
17044                     q++;
17045                     opt->value = n;
17046                 }
17047                 cps->comboList[cps->comboCnt++] = q;
17048             }
17049             cps->comboList[cps->comboCnt++] = NULL;
17050             opt->max = n + 1;
17051             opt->type = ComboBox;
17052         } else if(p = strstr(opt->name, " -button")) {
17053             opt->type = Button;
17054         } else if(p = strstr(opt->name, " -save")) {
17055             opt->type = SaveButton;
17056         } else return FALSE;
17057         *p = 0; // terminate option name
17058         // now look if the command-line options define a setting for this engine option.
17059         if(cps->optionSettings && cps->optionSettings[0])
17060             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17061         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17062           snprintf(buf, MSG_SIZ, "option %s", p);
17063                 if(p = strstr(buf, ",")) *p = 0;
17064                 if(q = strchr(buf, '=')) switch(opt->type) {
17065                     case ComboBox:
17066                         for(n=0; n<opt->max; n++)
17067                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17068                         break;
17069                     case TextBox:
17070                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17071                         break;
17072                     case Spin:
17073                     case CheckBox:
17074                         opt->value = atoi(q+1);
17075                     default:
17076                         break;
17077                 }
17078                 strcat(buf, "\n");
17079                 SendToProgram(buf, cps);
17080         }
17081         return TRUE;
17082 }
17083
17084 void
17085 FeatureDone (ChessProgramState *cps, int val)
17086 {
17087   DelayedEventCallback cb = GetDelayedEvent();
17088   if ((cb == InitBackEnd3 && cps == &first) ||
17089       (cb == SettingsMenuIfReady && cps == &second) ||
17090       (cb == LoadEngine) ||
17091       (cb == TwoMachinesEventIfReady)) {
17092     CancelDelayedEvent();
17093     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17094   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17095   cps->initDone = val;
17096   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17097 }
17098
17099 /* Parse feature command from engine */
17100 void
17101 ParseFeatures (char *args, ChessProgramState *cps)
17102 {
17103   char *p = args;
17104   char *q = NULL;
17105   int val;
17106   char buf[MSG_SIZ];
17107
17108   for (;;) {
17109     while (*p == ' ') p++;
17110     if (*p == NULLCHAR) return;
17111
17112     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17113     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17114     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17115     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17116     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17117     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17118     if (BoolFeature(&p, "reuse", &val, cps)) {
17119       /* Engine can disable reuse, but can't enable it if user said no */
17120       if (!val) cps->reuse = FALSE;
17121       continue;
17122     }
17123     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17124     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17125       if (gameMode == TwoMachinesPlay) {
17126         DisplayTwoMachinesTitle();
17127       } else {
17128         DisplayTitle("");
17129       }
17130       continue;
17131     }
17132     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17133     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17134     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17135     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17136     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17137     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17138     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17139     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17140     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17141     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17142     if (IntFeature(&p, "done", &val, cps)) {
17143       FeatureDone(cps, val);
17144       continue;
17145     }
17146     /* Added by Tord: */
17147     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17148     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17149     /* End of additions by Tord */
17150
17151     /* [HGM] added features: */
17152     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17153     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17154     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17155     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17156     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17157     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17158     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17159     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17160         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17161         FREE(cps->option[cps->nrOptions].name);
17162         cps->option[cps->nrOptions].name = q; q = NULL;
17163         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17164           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17165             SendToProgram(buf, cps);
17166             continue;
17167         }
17168         if(cps->nrOptions >= MAX_OPTIONS) {
17169             cps->nrOptions--;
17170             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17171             DisplayError(buf, 0);
17172         }
17173         continue;
17174     }
17175     /* End of additions by HGM */
17176
17177     /* unknown feature: complain and skip */
17178     q = p;
17179     while (*q && *q != '=') q++;
17180     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17181     SendToProgram(buf, cps);
17182     p = q;
17183     if (*p == '=') {
17184       p++;
17185       if (*p == '\"') {
17186         p++;
17187         while (*p && *p != '\"') p++;
17188         if (*p == '\"') p++;
17189       } else {
17190         while (*p && *p != ' ') p++;
17191       }
17192     }
17193   }
17194
17195 }
17196
17197 void
17198 PeriodicUpdatesEvent (int newState)
17199 {
17200     if (newState == appData.periodicUpdates)
17201       return;
17202
17203     appData.periodicUpdates=newState;
17204
17205     /* Display type changes, so update it now */
17206 //    DisplayAnalysis();
17207
17208     /* Get the ball rolling again... */
17209     if (newState) {
17210         AnalysisPeriodicEvent(1);
17211         StartAnalysisClock();
17212     }
17213 }
17214
17215 void
17216 PonderNextMoveEvent (int newState)
17217 {
17218     if (newState == appData.ponderNextMove) return;
17219     if (gameMode == EditPosition) EditPositionDone(TRUE);
17220     if (newState) {
17221         SendToProgram("hard\n", &first);
17222         if (gameMode == TwoMachinesPlay) {
17223             SendToProgram("hard\n", &second);
17224         }
17225     } else {
17226         SendToProgram("easy\n", &first);
17227         thinkOutput[0] = NULLCHAR;
17228         if (gameMode == TwoMachinesPlay) {
17229             SendToProgram("easy\n", &second);
17230         }
17231     }
17232     appData.ponderNextMove = newState;
17233 }
17234
17235 void
17236 NewSettingEvent (int option, int *feature, char *command, int value)
17237 {
17238     char buf[MSG_SIZ];
17239
17240     if (gameMode == EditPosition) EditPositionDone(TRUE);
17241     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17242     if(feature == NULL || *feature) SendToProgram(buf, &first);
17243     if (gameMode == TwoMachinesPlay) {
17244         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17245     }
17246 }
17247
17248 void
17249 ShowThinkingEvent ()
17250 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17251 {
17252     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17253     int newState = appData.showThinking
17254         // [HGM] thinking: other features now need thinking output as well
17255         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17256
17257     if (oldState == newState) return;
17258     oldState = newState;
17259     if (gameMode == EditPosition) EditPositionDone(TRUE);
17260     if (oldState) {
17261         SendToProgram("post\n", &first);
17262         if (gameMode == TwoMachinesPlay) {
17263             SendToProgram("post\n", &second);
17264         }
17265     } else {
17266         SendToProgram("nopost\n", &first);
17267         thinkOutput[0] = NULLCHAR;
17268         if (gameMode == TwoMachinesPlay) {
17269             SendToProgram("nopost\n", &second);
17270         }
17271     }
17272 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17273 }
17274
17275 void
17276 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17277 {
17278   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17279   if (pr == NoProc) return;
17280   AskQuestion(title, question, replyPrefix, pr);
17281 }
17282
17283 void
17284 TypeInEvent (char firstChar)
17285 {
17286     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17287         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17288         gameMode == AnalyzeMode || gameMode == EditGame ||
17289         gameMode == EditPosition || gameMode == IcsExamining ||
17290         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17291         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17292                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17293                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17294         gameMode == Training) PopUpMoveDialog(firstChar);
17295 }
17296
17297 void
17298 TypeInDoneEvent (char *move)
17299 {
17300         Board board;
17301         int n, fromX, fromY, toX, toY;
17302         char promoChar;
17303         ChessMove moveType;
17304
17305         // [HGM] FENedit
17306         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17307                 EditPositionPasteFEN(move);
17308                 return;
17309         }
17310         // [HGM] movenum: allow move number to be typed in any mode
17311         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17312           ToNrEvent(2*n-1);
17313           return;
17314         }
17315         // undocumented kludge: allow command-line option to be typed in!
17316         // (potentially fatal, and does not implement the effect of the option.)
17317         // should only be used for options that are values on which future decisions will be made,
17318         // and definitely not on options that would be used during initialization.
17319         if(strstr(move, "!!! -") == move) {
17320             ParseArgsFromString(move+4);
17321             return;
17322         }
17323
17324       if (gameMode != EditGame && currentMove != forwardMostMove &&
17325         gameMode != Training) {
17326         DisplayMoveError(_("Displayed move is not current"));
17327       } else {
17328         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17329           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17330         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17331         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17332           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17333           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17334         } else {
17335           DisplayMoveError(_("Could not parse move"));
17336         }
17337       }
17338 }
17339
17340 void
17341 DisplayMove (int moveNumber)
17342 {
17343     char message[MSG_SIZ];
17344     char res[MSG_SIZ];
17345     char cpThinkOutput[MSG_SIZ];
17346
17347     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17348
17349     if (moveNumber == forwardMostMove - 1 ||
17350         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17351
17352         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17353
17354         if (strchr(cpThinkOutput, '\n')) {
17355             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17356         }
17357     } else {
17358         *cpThinkOutput = NULLCHAR;
17359     }
17360
17361     /* [AS] Hide thinking from human user */
17362     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17363         *cpThinkOutput = NULLCHAR;
17364         if( thinkOutput[0] != NULLCHAR ) {
17365             int i;
17366
17367             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17368                 cpThinkOutput[i] = '.';
17369             }
17370             cpThinkOutput[i] = NULLCHAR;
17371             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17372         }
17373     }
17374
17375     if (moveNumber == forwardMostMove - 1 &&
17376         gameInfo.resultDetails != NULL) {
17377         if (gameInfo.resultDetails[0] == NULLCHAR) {
17378           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17379         } else {
17380           snprintf(res, MSG_SIZ, " {%s} %s",
17381                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17382         }
17383     } else {
17384         res[0] = NULLCHAR;
17385     }
17386
17387     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17388         DisplayMessage(res, cpThinkOutput);
17389     } else {
17390       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17391                 WhiteOnMove(moveNumber) ? " " : ".. ",
17392                 parseList[moveNumber], res);
17393         DisplayMessage(message, cpThinkOutput);
17394     }
17395 }
17396
17397 void
17398 DisplayComment (int moveNumber, char *text)
17399 {
17400     char title[MSG_SIZ];
17401
17402     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17403       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17404     } else {
17405       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17406               WhiteOnMove(moveNumber) ? " " : ".. ",
17407               parseList[moveNumber]);
17408     }
17409     if (text != NULL && (appData.autoDisplayComment || commentUp))
17410         CommentPopUp(title, text);
17411 }
17412
17413 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17414  * might be busy thinking or pondering.  It can be omitted if your
17415  * gnuchess is configured to stop thinking immediately on any user
17416  * input.  However, that gnuchess feature depends on the FIONREAD
17417  * ioctl, which does not work properly on some flavors of Unix.
17418  */
17419 void
17420 Attention (ChessProgramState *cps)
17421 {
17422 #if ATTENTION
17423     if (!cps->useSigint) return;
17424     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17425     switch (gameMode) {
17426       case MachinePlaysWhite:
17427       case MachinePlaysBlack:
17428       case TwoMachinesPlay:
17429       case IcsPlayingWhite:
17430       case IcsPlayingBlack:
17431       case AnalyzeMode:
17432       case AnalyzeFile:
17433         /* Skip if we know it isn't thinking */
17434         if (!cps->maybeThinking) return;
17435         if (appData.debugMode)
17436           fprintf(debugFP, "Interrupting %s\n", cps->which);
17437         InterruptChildProcess(cps->pr);
17438         cps->maybeThinking = FALSE;
17439         break;
17440       default:
17441         break;
17442     }
17443 #endif /*ATTENTION*/
17444 }
17445
17446 int
17447 CheckFlags ()
17448 {
17449     if (whiteTimeRemaining <= 0) {
17450         if (!whiteFlag) {
17451             whiteFlag = TRUE;
17452             if (appData.icsActive) {
17453                 if (appData.autoCallFlag &&
17454                     gameMode == IcsPlayingBlack && !blackFlag) {
17455                   SendToICS(ics_prefix);
17456                   SendToICS("flag\n");
17457                 }
17458             } else {
17459                 if (blackFlag) {
17460                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17461                 } else {
17462                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17463                     if (appData.autoCallFlag) {
17464                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17465                         return TRUE;
17466                     }
17467                 }
17468             }
17469         }
17470     }
17471     if (blackTimeRemaining <= 0) {
17472         if (!blackFlag) {
17473             blackFlag = TRUE;
17474             if (appData.icsActive) {
17475                 if (appData.autoCallFlag &&
17476                     gameMode == IcsPlayingWhite && !whiteFlag) {
17477                   SendToICS(ics_prefix);
17478                   SendToICS("flag\n");
17479                 }
17480             } else {
17481                 if (whiteFlag) {
17482                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17483                 } else {
17484                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17485                     if (appData.autoCallFlag) {
17486                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17487                         return TRUE;
17488                     }
17489                 }
17490             }
17491         }
17492     }
17493     return FALSE;
17494 }
17495
17496 void
17497 CheckTimeControl ()
17498 {
17499     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17500         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17501
17502     /*
17503      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17504      */
17505     if ( !WhiteOnMove(forwardMostMove) ) {
17506         /* White made time control */
17507         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17508         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17509         /* [HGM] time odds: correct new time quota for time odds! */
17510                                             / WhitePlayer()->timeOdds;
17511         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17512     } else {
17513         lastBlack -= blackTimeRemaining;
17514         /* Black made time control */
17515         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17516                                             / WhitePlayer()->other->timeOdds;
17517         lastWhite = whiteTimeRemaining;
17518     }
17519 }
17520
17521 void
17522 DisplayBothClocks ()
17523 {
17524     int wom = gameMode == EditPosition ?
17525       !blackPlaysFirst : WhiteOnMove(currentMove);
17526     DisplayWhiteClock(whiteTimeRemaining, wom);
17527     DisplayBlackClock(blackTimeRemaining, !wom);
17528 }
17529
17530
17531 /* Timekeeping seems to be a portability nightmare.  I think everyone
17532    has ftime(), but I'm really not sure, so I'm including some ifdefs
17533    to use other calls if you don't.  Clocks will be less accurate if
17534    you have neither ftime nor gettimeofday.
17535 */
17536
17537 /* VS 2008 requires the #include outside of the function */
17538 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17539 #include <sys/timeb.h>
17540 #endif
17541
17542 /* Get the current time as a TimeMark */
17543 void
17544 GetTimeMark (TimeMark *tm)
17545 {
17546 #if HAVE_GETTIMEOFDAY
17547
17548     struct timeval timeVal;
17549     struct timezone timeZone;
17550
17551     gettimeofday(&timeVal, &timeZone);
17552     tm->sec = (long) timeVal.tv_sec;
17553     tm->ms = (int) (timeVal.tv_usec / 1000L);
17554
17555 #else /*!HAVE_GETTIMEOFDAY*/
17556 #if HAVE_FTIME
17557
17558 // include <sys/timeb.h> / moved to just above start of function
17559     struct timeb timeB;
17560
17561     ftime(&timeB);
17562     tm->sec = (long) timeB.time;
17563     tm->ms = (int) timeB.millitm;
17564
17565 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17566     tm->sec = (long) time(NULL);
17567     tm->ms = 0;
17568 #endif
17569 #endif
17570 }
17571
17572 /* Return the difference in milliseconds between two
17573    time marks.  We assume the difference will fit in a long!
17574 */
17575 long
17576 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17577 {
17578     return 1000L*(tm2->sec - tm1->sec) +
17579            (long) (tm2->ms - tm1->ms);
17580 }
17581
17582
17583 /*
17584  * Code to manage the game clocks.
17585  *
17586  * In tournament play, black starts the clock and then white makes a move.
17587  * We give the human user a slight advantage if he is playing white---the
17588  * clocks don't run until he makes his first move, so it takes zero time.
17589  * Also, we don't account for network lag, so we could get out of sync
17590  * with GNU Chess's clock -- but then, referees are always right.
17591  */
17592
17593 static TimeMark tickStartTM;
17594 static long intendedTickLength;
17595
17596 long
17597 NextTickLength (long timeRemaining)
17598 {
17599     long nominalTickLength, nextTickLength;
17600
17601     if (timeRemaining > 0L && timeRemaining <= 10000L)
17602       nominalTickLength = 100L;
17603     else
17604       nominalTickLength = 1000L;
17605     nextTickLength = timeRemaining % nominalTickLength;
17606     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17607
17608     return nextTickLength;
17609 }
17610
17611 /* Adjust clock one minute up or down */
17612 void
17613 AdjustClock (Boolean which, int dir)
17614 {
17615     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17616     if(which) blackTimeRemaining += 60000*dir;
17617     else      whiteTimeRemaining += 60000*dir;
17618     DisplayBothClocks();
17619     adjustedClock = TRUE;
17620 }
17621
17622 /* Stop clocks and reset to a fresh time control */
17623 void
17624 ResetClocks ()
17625 {
17626     (void) StopClockTimer();
17627     if (appData.icsActive) {
17628         whiteTimeRemaining = blackTimeRemaining = 0;
17629     } else if (searchTime) {
17630         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17631         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17632     } else { /* [HGM] correct new time quote for time odds */
17633         whiteTC = blackTC = fullTimeControlString;
17634         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17635         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17636     }
17637     if (whiteFlag || blackFlag) {
17638         DisplayTitle("");
17639         whiteFlag = blackFlag = FALSE;
17640     }
17641     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17642     DisplayBothClocks();
17643     adjustedClock = FALSE;
17644 }
17645
17646 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17647
17648 /* Decrement running clock by amount of time that has passed */
17649 void
17650 DecrementClocks ()
17651 {
17652     long timeRemaining;
17653     long lastTickLength, fudge;
17654     TimeMark now;
17655
17656     if (!appData.clockMode) return;
17657     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17658
17659     GetTimeMark(&now);
17660
17661     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17662
17663     /* Fudge if we woke up a little too soon */
17664     fudge = intendedTickLength - lastTickLength;
17665     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17666
17667     if (WhiteOnMove(forwardMostMove)) {
17668         if(whiteNPS >= 0) lastTickLength = 0;
17669         timeRemaining = whiteTimeRemaining -= lastTickLength;
17670         if(timeRemaining < 0 && !appData.icsActive) {
17671             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17672             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17673                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17674                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17675             }
17676         }
17677         DisplayWhiteClock(whiteTimeRemaining - fudge,
17678                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17679     } else {
17680         if(blackNPS >= 0) lastTickLength = 0;
17681         timeRemaining = blackTimeRemaining -= lastTickLength;
17682         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17683             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17684             if(suddenDeath) {
17685                 blackStartMove = forwardMostMove;
17686                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17687             }
17688         }
17689         DisplayBlackClock(blackTimeRemaining - fudge,
17690                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17691     }
17692     if (CheckFlags()) return;
17693
17694     if(twoBoards) { // count down secondary board's clocks as well
17695         activePartnerTime -= lastTickLength;
17696         partnerUp = 1;
17697         if(activePartner == 'W')
17698             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17699         else
17700             DisplayBlackClock(activePartnerTime, TRUE);
17701         partnerUp = 0;
17702     }
17703
17704     tickStartTM = now;
17705     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17706     StartClockTimer(intendedTickLength);
17707
17708     /* if the time remaining has fallen below the alarm threshold, sound the
17709      * alarm. if the alarm has sounded and (due to a takeback or time control
17710      * with increment) the time remaining has increased to a level above the
17711      * threshold, reset the alarm so it can sound again.
17712      */
17713
17714     if (appData.icsActive && appData.icsAlarm) {
17715
17716         /* make sure we are dealing with the user's clock */
17717         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17718                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17719            )) return;
17720
17721         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17722             alarmSounded = FALSE;
17723         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17724             PlayAlarmSound();
17725             alarmSounded = TRUE;
17726         }
17727     }
17728 }
17729
17730
17731 /* A player has just moved, so stop the previously running
17732    clock and (if in clock mode) start the other one.
17733    We redisplay both clocks in case we're in ICS mode, because
17734    ICS gives us an update to both clocks after every move.
17735    Note that this routine is called *after* forwardMostMove
17736    is updated, so the last fractional tick must be subtracted
17737    from the color that is *not* on move now.
17738 */
17739 void
17740 SwitchClocks (int newMoveNr)
17741 {
17742     long lastTickLength;
17743     TimeMark now;
17744     int flagged = FALSE;
17745
17746     GetTimeMark(&now);
17747
17748     if (StopClockTimer() && appData.clockMode) {
17749         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17750         if (!WhiteOnMove(forwardMostMove)) {
17751             if(blackNPS >= 0) lastTickLength = 0;
17752             blackTimeRemaining -= lastTickLength;
17753            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17754 //         if(pvInfoList[forwardMostMove].time == -1)
17755                  pvInfoList[forwardMostMove].time =               // use GUI time
17756                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17757         } else {
17758            if(whiteNPS >= 0) lastTickLength = 0;
17759            whiteTimeRemaining -= lastTickLength;
17760            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17761 //         if(pvInfoList[forwardMostMove].time == -1)
17762                  pvInfoList[forwardMostMove].time =
17763                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17764         }
17765         flagged = CheckFlags();
17766     }
17767     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17768     CheckTimeControl();
17769
17770     if (flagged || !appData.clockMode) return;
17771
17772     switch (gameMode) {
17773       case MachinePlaysBlack:
17774       case MachinePlaysWhite:
17775       case BeginningOfGame:
17776         if (pausing) return;
17777         break;
17778
17779       case EditGame:
17780       case PlayFromGameFile:
17781       case IcsExamining:
17782         return;
17783
17784       default:
17785         break;
17786     }
17787
17788     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17789         if(WhiteOnMove(forwardMostMove))
17790              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17791         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17792     }
17793
17794     tickStartTM = now;
17795     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17796       whiteTimeRemaining : blackTimeRemaining);
17797     StartClockTimer(intendedTickLength);
17798 }
17799
17800
17801 /* Stop both clocks */
17802 void
17803 StopClocks ()
17804 {
17805     long lastTickLength;
17806     TimeMark now;
17807
17808     if (!StopClockTimer()) return;
17809     if (!appData.clockMode) return;
17810
17811     GetTimeMark(&now);
17812
17813     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17814     if (WhiteOnMove(forwardMostMove)) {
17815         if(whiteNPS >= 0) lastTickLength = 0;
17816         whiteTimeRemaining -= lastTickLength;
17817         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17818     } else {
17819         if(blackNPS >= 0) lastTickLength = 0;
17820         blackTimeRemaining -= lastTickLength;
17821         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17822     }
17823     CheckFlags();
17824 }
17825
17826 /* Start clock of player on move.  Time may have been reset, so
17827    if clock is already running, stop and restart it. */
17828 void
17829 StartClocks ()
17830 {
17831     (void) StopClockTimer(); /* in case it was running already */
17832     DisplayBothClocks();
17833     if (CheckFlags()) return;
17834
17835     if (!appData.clockMode) return;
17836     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17837
17838     GetTimeMark(&tickStartTM);
17839     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17840       whiteTimeRemaining : blackTimeRemaining);
17841
17842    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17843     whiteNPS = blackNPS = -1;
17844     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17845        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17846         whiteNPS = first.nps;
17847     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17848        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17849         blackNPS = first.nps;
17850     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17851         whiteNPS = second.nps;
17852     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17853         blackNPS = second.nps;
17854     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17855
17856     StartClockTimer(intendedTickLength);
17857 }
17858
17859 char *
17860 TimeString (long ms)
17861 {
17862     long second, minute, hour, day;
17863     char *sign = "";
17864     static char buf[32];
17865
17866     if (ms > 0 && ms <= 9900) {
17867       /* convert milliseconds to tenths, rounding up */
17868       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17869
17870       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17871       return buf;
17872     }
17873
17874     /* convert milliseconds to seconds, rounding up */
17875     /* use floating point to avoid strangeness of integer division
17876        with negative dividends on many machines */
17877     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17878
17879     if (second < 0) {
17880         sign = "-";
17881         second = -second;
17882     }
17883
17884     day = second / (60 * 60 * 24);
17885     second = second % (60 * 60 * 24);
17886     hour = second / (60 * 60);
17887     second = second % (60 * 60);
17888     minute = second / 60;
17889     second = second % 60;
17890
17891     if (day > 0)
17892       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17893               sign, day, hour, minute, second);
17894     else if (hour > 0)
17895       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17896     else
17897       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17898
17899     return buf;
17900 }
17901
17902
17903 /*
17904  * This is necessary because some C libraries aren't ANSI C compliant yet.
17905  */
17906 char *
17907 StrStr (char *string, char *match)
17908 {
17909     int i, length;
17910
17911     length = strlen(match);
17912
17913     for (i = strlen(string) - length; i >= 0; i--, string++)
17914       if (!strncmp(match, string, length))
17915         return string;
17916
17917     return NULL;
17918 }
17919
17920 char *
17921 StrCaseStr (char *string, char *match)
17922 {
17923     int i, j, length;
17924
17925     length = strlen(match);
17926
17927     for (i = strlen(string) - length; i >= 0; i--, string++) {
17928         for (j = 0; j < length; j++) {
17929             if (ToLower(match[j]) != ToLower(string[j]))
17930               break;
17931         }
17932         if (j == length) return string;
17933     }
17934
17935     return NULL;
17936 }
17937
17938 #ifndef _amigados
17939 int
17940 StrCaseCmp (char *s1, char *s2)
17941 {
17942     char c1, c2;
17943
17944     for (;;) {
17945         c1 = ToLower(*s1++);
17946         c2 = ToLower(*s2++);
17947         if (c1 > c2) return 1;
17948         if (c1 < c2) return -1;
17949         if (c1 == NULLCHAR) return 0;
17950     }
17951 }
17952
17953
17954 int
17955 ToLower (int c)
17956 {
17957     return isupper(c) ? tolower(c) : c;
17958 }
17959
17960
17961 int
17962 ToUpper (int c)
17963 {
17964     return islower(c) ? toupper(c) : c;
17965 }
17966 #endif /* !_amigados    */
17967
17968 char *
17969 StrSave (char *s)
17970 {
17971   char *ret;
17972
17973   if ((ret = (char *) malloc(strlen(s) + 1)))
17974     {
17975       safeStrCpy(ret, s, strlen(s)+1);
17976     }
17977   return ret;
17978 }
17979
17980 char *
17981 StrSavePtr (char *s, char **savePtr)
17982 {
17983     if (*savePtr) {
17984         free(*savePtr);
17985     }
17986     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17987       safeStrCpy(*savePtr, s, strlen(s)+1);
17988     }
17989     return(*savePtr);
17990 }
17991
17992 char *
17993 PGNDate ()
17994 {
17995     time_t clock;
17996     struct tm *tm;
17997     char buf[MSG_SIZ];
17998
17999     clock = time((time_t *)NULL);
18000     tm = localtime(&clock);
18001     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18002             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18003     return StrSave(buf);
18004 }
18005
18006
18007 char *
18008 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18009 {
18010     int i, j, fromX, fromY, toX, toY;
18011     int whiteToPlay, haveRights = nrCastlingRights;
18012     char buf[MSG_SIZ];
18013     char *p, *q;
18014     int emptycount;
18015     ChessSquare piece;
18016
18017     whiteToPlay = (gameMode == EditPosition) ?
18018       !blackPlaysFirst : (move % 2 == 0);
18019     p = buf;
18020
18021     /* Piece placement data */
18022     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18023         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18024         emptycount = 0;
18025         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18026             if (boards[move][i][j] == EmptySquare) {
18027                 emptycount++;
18028             } else { ChessSquare piece = boards[move][i][j];
18029                 if (emptycount > 0) {
18030                     if(emptycount<10) /* [HGM] can be >= 10 */
18031                         *p++ = '0' + emptycount;
18032                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18033                     emptycount = 0;
18034                 }
18035                 if(PieceToChar(piece) == '+') {
18036                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18037                     *p++ = '+';
18038                     piece = (ChessSquare)(CHUDEMOTED(piece));
18039                 }
18040                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18041                 if(*p = PieceSuffix(piece)) p++;
18042                 if(p[-1] == '~') {
18043                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18044                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18045                     *p++ = '~';
18046                 }
18047             }
18048         }
18049         if (emptycount > 0) {
18050             if(emptycount<10) /* [HGM] can be >= 10 */
18051                 *p++ = '0' + emptycount;
18052             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18053             emptycount = 0;
18054         }
18055         *p++ = '/';
18056     }
18057     *(p - 1) = ' ';
18058
18059     /* [HGM] print Crazyhouse or Shogi holdings */
18060     if( gameInfo.holdingsWidth ) {
18061         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18062         q = p;
18063         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18064             piece = boards[move][i][BOARD_WIDTH-1];
18065             if( piece != EmptySquare )
18066               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18067                   *p++ = PieceToChar(piece);
18068         }
18069         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18070             piece = boards[move][BOARD_HEIGHT-i-1][0];
18071             if( piece != EmptySquare )
18072               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18073                   *p++ = PieceToChar(piece);
18074         }
18075
18076         if( q == p ) *p++ = '-';
18077         *p++ = ']';
18078         *p++ = ' ';
18079     }
18080
18081     /* Active color */
18082     *p++ = whiteToPlay ? 'w' : 'b';
18083     *p++ = ' ';
18084
18085   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18086     haveRights = 0; q = p;
18087     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18088       piece = boards[move][0][i];
18089       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18090         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18091       }
18092     }
18093     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18094       piece = boards[move][BOARD_HEIGHT-1][i];
18095       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18096         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18097       }
18098     }
18099     if(p == q) *p++ = '-';
18100     *p++ = ' ';
18101   }
18102
18103   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18104     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18105   } else {
18106   if(haveRights) {
18107      int handW=0, handB=0;
18108      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18109         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18110         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18111      }
18112      q = p;
18113      if(appData.fischerCastling) {
18114         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18115            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18116                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18117         } else {
18118        /* [HGM] write directly from rights */
18119            if(boards[move][CASTLING][2] != NoRights &&
18120               boards[move][CASTLING][0] != NoRights   )
18121                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18122            if(boards[move][CASTLING][2] != NoRights &&
18123               boards[move][CASTLING][1] != NoRights   )
18124                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18125         }
18126         if(handB) {
18127            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18128                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18129         } else {
18130            if(boards[move][CASTLING][5] != NoRights &&
18131               boards[move][CASTLING][3] != NoRights   )
18132                 *p++ = boards[move][CASTLING][3] + AAA;
18133            if(boards[move][CASTLING][5] != NoRights &&
18134               boards[move][CASTLING][4] != NoRights   )
18135                 *p++ = boards[move][CASTLING][4] + AAA;
18136         }
18137      } else {
18138
18139         /* [HGM] write true castling rights */
18140         if( nrCastlingRights == 6 ) {
18141             int q, k=0;
18142             if(boards[move][CASTLING][0] != NoRights &&
18143                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18144             q = (boards[move][CASTLING][1] != NoRights &&
18145                  boards[move][CASTLING][2] != NoRights  );
18146             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18147                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18148                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18149                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18150             }
18151             if(q) *p++ = 'Q';
18152             k = 0;
18153             if(boards[move][CASTLING][3] != NoRights &&
18154                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18155             q = (boards[move][CASTLING][4] != NoRights &&
18156                  boards[move][CASTLING][5] != NoRights  );
18157             if(handB) {
18158                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18159                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18160                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18161             }
18162             if(q) *p++ = 'q';
18163         }
18164      }
18165      if (q == p) *p++ = '-'; /* No castling rights */
18166      *p++ = ' ';
18167   }
18168
18169   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18170      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18171      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18172     /* En passant target square */
18173     if (move > backwardMostMove) {
18174         fromX = moveList[move - 1][0] - AAA;
18175         fromY = moveList[move - 1][1] - ONE;
18176         toX = moveList[move - 1][2] - AAA;
18177         toY = moveList[move - 1][3] - ONE;
18178         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18179             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18180             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18181             fromX == toX) {
18182             /* 2-square pawn move just happened */
18183             *p++ = toX + AAA;
18184             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18185         } else {
18186             *p++ = '-';
18187         }
18188     } else if(move == backwardMostMove) {
18189         // [HGM] perhaps we should always do it like this, and forget the above?
18190         if((signed char)boards[move][EP_STATUS] >= 0) {
18191             *p++ = boards[move][EP_STATUS] + AAA;
18192             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18193         } else {
18194             *p++ = '-';
18195         }
18196     } else {
18197         *p++ = '-';
18198     }
18199     *p++ = ' ';
18200   }
18201   }
18202
18203     if(moveCounts)
18204     {   int i = 0, j=move;
18205
18206         /* [HGM] find reversible plies */
18207         if (appData.debugMode) { int k;
18208             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18209             for(k=backwardMostMove; k<=forwardMostMove; k++)
18210                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18211
18212         }
18213
18214         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18215         if( j == backwardMostMove ) i += initialRulePlies;
18216         sprintf(p, "%d ", i);
18217         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18218
18219         /* Fullmove number */
18220         sprintf(p, "%d", (move / 2) + 1);
18221     } else *--p = NULLCHAR;
18222
18223     return StrSave(buf);
18224 }
18225
18226 Boolean
18227 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18228 {
18229     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18230     char *p, c;
18231     int emptycount, virgin[BOARD_FILES];
18232     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18233
18234     p = fen;
18235
18236     /* Piece placement data */
18237     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18238         j = 0;
18239         for (;;) {
18240             if (*p == '/' || *p == ' ' || *p == '[' ) {
18241                 if(j > w) w = j;
18242                 emptycount = gameInfo.boardWidth - j;
18243                 while (emptycount--)
18244                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18245                 if (*p == '/') p++;
18246                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18247                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18248                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18249                     }
18250                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18251                 }
18252                 break;
18253 #if(BOARD_FILES >= 10)*0
18254             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18255                 p++; emptycount=10;
18256                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18257                 while (emptycount--)
18258                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18259 #endif
18260             } else if (*p == '*') {
18261                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18262             } else if (isdigit(*p)) {
18263                 emptycount = *p++ - '0';
18264                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18265                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18266                 while (emptycount--)
18267                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18268             } else if (*p == '<') {
18269                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18270                 else if (i != 0 || !shuffle) return FALSE;
18271                 p++;
18272             } else if (shuffle && *p == '>') {
18273                 p++; // for now ignore closing shuffle range, and assume rank-end
18274             } else if (*p == '?') {
18275                 if (j >= gameInfo.boardWidth) return FALSE;
18276                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18277                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18278             } else if (*p == '+' || isalpha(*p)) {
18279                 char *q, *s = SUFFIXES;
18280                 if (j >= gameInfo.boardWidth) return FALSE;
18281                 if(*p=='+') {
18282                     char c = *++p;
18283                     if(q = strchr(s, p[1])) p++;
18284                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18285                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18286                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18287                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18288                 } else {
18289                     char c = *p++;
18290                     if(q = strchr(s, *p)) p++;
18291                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18292                 }
18293
18294                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18295                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18296                     piece = (ChessSquare) (PROMOTED(piece));
18297                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18298                     p++;
18299                 }
18300                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18301                 if(piece == king) wKingRank = i;
18302                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18303             } else {
18304                 return FALSE;
18305             }
18306         }
18307     }
18308     while (*p == '/' || *p == ' ') p++;
18309
18310     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18311
18312     /* [HGM] by default clear Crazyhouse holdings, if present */
18313     if(gameInfo.holdingsWidth) {
18314        for(i=0; i<BOARD_HEIGHT; i++) {
18315            board[i][0]             = EmptySquare; /* black holdings */
18316            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18317            board[i][1]             = (ChessSquare) 0; /* black counts */
18318            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18319        }
18320     }
18321
18322     /* [HGM] look for Crazyhouse holdings here */
18323     while(*p==' ') p++;
18324     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18325         int swap=0, wcnt=0, bcnt=0;
18326         if(*p == '[') p++;
18327         if(*p == '<') swap++, p++;
18328         if(*p == '-' ) p++; /* empty holdings */ else {
18329             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18330             /* if we would allow FEN reading to set board size, we would   */
18331             /* have to add holdings and shift the board read so far here   */
18332             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18333                 p++;
18334                 if((int) piece >= (int) BlackPawn ) {
18335                     i = (int)piece - (int)BlackPawn;
18336                     i = PieceToNumber((ChessSquare)i);
18337                     if( i >= gameInfo.holdingsSize ) return FALSE;
18338                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18339                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18340                     bcnt++;
18341                 } else {
18342                     i = (int)piece - (int)WhitePawn;
18343                     i = PieceToNumber((ChessSquare)i);
18344                     if( i >= gameInfo.holdingsSize ) return FALSE;
18345                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18346                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18347                     wcnt++;
18348                 }
18349             }
18350             if(subst) { // substitute back-rank question marks by holdings pieces
18351                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18352                     int k, m, n = bcnt + 1;
18353                     if(board[0][j] == ClearBoard) {
18354                         if(!wcnt) return FALSE;
18355                         n = rand() % wcnt;
18356                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18357                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18358                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18359                             break;
18360                         }
18361                     }
18362                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18363                         if(!bcnt) return FALSE;
18364                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18365                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18366                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18367                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18368                             break;
18369                         }
18370                     }
18371                 }
18372                 subst = 0;
18373             }
18374         }
18375         if(*p == ']') p++;
18376     }
18377
18378     if(subst) return FALSE; // substitution requested, but no holdings
18379
18380     while(*p == ' ') p++;
18381
18382     /* Active color */
18383     c = *p++;
18384     if(appData.colorNickNames) {
18385       if( c == appData.colorNickNames[0] ) c = 'w'; else
18386       if( c == appData.colorNickNames[1] ) c = 'b';
18387     }
18388     switch (c) {
18389       case 'w':
18390         *blackPlaysFirst = FALSE;
18391         break;
18392       case 'b':
18393         *blackPlaysFirst = TRUE;
18394         break;
18395       default:
18396         return FALSE;
18397     }
18398
18399     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18400     /* return the extra info in global variiables             */
18401
18402     while(*p==' ') p++;
18403
18404     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18405         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18406         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18407     }
18408
18409     /* set defaults in case FEN is incomplete */
18410     board[EP_STATUS] = EP_UNKNOWN;
18411     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18412     for(i=0; i<nrCastlingRights; i++ ) {
18413         board[CASTLING][i] =
18414             appData.fischerCastling ? NoRights : initialRights[i];
18415     }   /* assume possible unless obviously impossible */
18416     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18417     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18418     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18419                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18420     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18421     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18422     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18423                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18424     FENrulePlies = 0;
18425
18426     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18427       char *q = p;
18428       int w=0, b=0;
18429       while(isalpha(*p)) {
18430         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18431         if(islower(*p)) b |= 1 << (*p++ - 'a');
18432       }
18433       if(*p == '-') p++;
18434       if(p != q) {
18435         board[TOUCHED_W] = ~w;
18436         board[TOUCHED_B] = ~b;
18437         while(*p == ' ') p++;
18438       }
18439     } else
18440
18441     if(nrCastlingRights) {
18442       int fischer = 0;
18443       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18444       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18445           /* castling indicator present, so default becomes no castlings */
18446           for(i=0; i<nrCastlingRights; i++ ) {
18447                  board[CASTLING][i] = NoRights;
18448           }
18449       }
18450       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18451              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18452              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18453              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18454         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18455
18456         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18457             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18458             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18459         }
18460         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18461             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18462         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18463                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18464         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18465                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18466         switch(c) {
18467           case'K':
18468               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18469               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18470               board[CASTLING][2] = whiteKingFile;
18471               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18472               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18473               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18474               break;
18475           case'Q':
18476               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18477               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18478               board[CASTLING][2] = whiteKingFile;
18479               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18480               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18481               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18482               break;
18483           case'k':
18484               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18485               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18486               board[CASTLING][5] = blackKingFile;
18487               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18488               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18489               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18490               break;
18491           case'q':
18492               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18493               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18494               board[CASTLING][5] = blackKingFile;
18495               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18496               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18497               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18498           case '-':
18499               break;
18500           default: /* FRC castlings */
18501               if(c >= 'a') { /* black rights */
18502                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18503                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18504                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18505                   if(i == BOARD_RGHT) break;
18506                   board[CASTLING][5] = i;
18507                   c -= AAA;
18508                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18509                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18510                   if(c > i)
18511                       board[CASTLING][3] = c;
18512                   else
18513                       board[CASTLING][4] = c;
18514               } else { /* white rights */
18515                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18516                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18517                     if(board[0][i] == WhiteKing) break;
18518                   if(i == BOARD_RGHT) break;
18519                   board[CASTLING][2] = i;
18520                   c -= AAA - 'a' + 'A';
18521                   if(board[0][c] >= WhiteKing) break;
18522                   if(c > i)
18523                       board[CASTLING][0] = c;
18524                   else
18525                       board[CASTLING][1] = c;
18526               }
18527         }
18528       }
18529       for(i=0; i<nrCastlingRights; i++)
18530         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18531       if(gameInfo.variant == VariantSChess)
18532         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18533       if(fischer && shuffle) appData.fischerCastling = TRUE;
18534     if (appData.debugMode) {
18535         fprintf(debugFP, "FEN castling rights:");
18536         for(i=0; i<nrCastlingRights; i++)
18537         fprintf(debugFP, " %d", board[CASTLING][i]);
18538         fprintf(debugFP, "\n");
18539     }
18540
18541       while(*p==' ') p++;
18542     }
18543
18544     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18545
18546     /* read e.p. field in games that know e.p. capture */
18547     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18548        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18549        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18550       if(*p=='-') {
18551         p++; board[EP_STATUS] = EP_NONE;
18552       } else {
18553          char c = *p++ - AAA;
18554
18555          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18556          if(*p >= '0' && *p <='9') p++;
18557          board[EP_STATUS] = c;
18558       }
18559     }
18560
18561
18562     if(sscanf(p, "%d", &i) == 1) {
18563         FENrulePlies = i; /* 50-move ply counter */
18564         /* (The move number is still ignored)    */
18565     }
18566
18567     return TRUE;
18568 }
18569
18570 void
18571 EditPositionPasteFEN (char *fen)
18572 {
18573   if (fen != NULL) {
18574     Board initial_position;
18575
18576     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18577       DisplayError(_("Bad FEN position in clipboard"), 0);
18578       return ;
18579     } else {
18580       int savedBlackPlaysFirst = blackPlaysFirst;
18581       EditPositionEvent();
18582       blackPlaysFirst = savedBlackPlaysFirst;
18583       CopyBoard(boards[0], initial_position);
18584       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18585       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18586       DisplayBothClocks();
18587       DrawPosition(FALSE, boards[currentMove]);
18588     }
18589   }
18590 }
18591
18592 static char cseq[12] = "\\   ";
18593
18594 Boolean
18595 set_cont_sequence (char *new_seq)
18596 {
18597     int len;
18598     Boolean ret;
18599
18600     // handle bad attempts to set the sequence
18601         if (!new_seq)
18602                 return 0; // acceptable error - no debug
18603
18604     len = strlen(new_seq);
18605     ret = (len > 0) && (len < sizeof(cseq));
18606     if (ret)
18607       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18608     else if (appData.debugMode)
18609       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18610     return ret;
18611 }
18612
18613 /*
18614     reformat a source message so words don't cross the width boundary.  internal
18615     newlines are not removed.  returns the wrapped size (no null character unless
18616     included in source message).  If dest is NULL, only calculate the size required
18617     for the dest buffer.  lp argument indicats line position upon entry, and it's
18618     passed back upon exit.
18619 */
18620 int
18621 wrap (char *dest, char *src, int count, int width, int *lp)
18622 {
18623     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18624
18625     cseq_len = strlen(cseq);
18626     old_line = line = *lp;
18627     ansi = len = clen = 0;
18628
18629     for (i=0; i < count; i++)
18630     {
18631         if (src[i] == '\033')
18632             ansi = 1;
18633
18634         // if we hit the width, back up
18635         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18636         {
18637             // store i & len in case the word is too long
18638             old_i = i, old_len = len;
18639
18640             // find the end of the last word
18641             while (i && src[i] != ' ' && src[i] != '\n')
18642             {
18643                 i--;
18644                 len--;
18645             }
18646
18647             // word too long?  restore i & len before splitting it
18648             if ((old_i-i+clen) >= width)
18649             {
18650                 i = old_i;
18651                 len = old_len;
18652             }
18653
18654             // extra space?
18655             if (i && src[i-1] == ' ')
18656                 len--;
18657
18658             if (src[i] != ' ' && src[i] != '\n')
18659             {
18660                 i--;
18661                 if (len)
18662                     len--;
18663             }
18664
18665             // now append the newline and continuation sequence
18666             if (dest)
18667                 dest[len] = '\n';
18668             len++;
18669             if (dest)
18670                 strncpy(dest+len, cseq, cseq_len);
18671             len += cseq_len;
18672             line = cseq_len;
18673             clen = cseq_len;
18674             continue;
18675         }
18676
18677         if (dest)
18678             dest[len] = src[i];
18679         len++;
18680         if (!ansi)
18681             line++;
18682         if (src[i] == '\n')
18683             line = 0;
18684         if (src[i] == 'm')
18685             ansi = 0;
18686     }
18687     if (dest && appData.debugMode)
18688     {
18689         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18690             count, width, line, len, *lp);
18691         show_bytes(debugFP, src, count);
18692         fprintf(debugFP, "\ndest: ");
18693         show_bytes(debugFP, dest, len);
18694         fprintf(debugFP, "\n");
18695     }
18696     *lp = dest ? line : old_line;
18697
18698     return len;
18699 }
18700
18701 // [HGM] vari: routines for shelving variations
18702 Boolean modeRestore = FALSE;
18703
18704 void
18705 PushInner (int firstMove, int lastMove)
18706 {
18707         int i, j, nrMoves = lastMove - firstMove;
18708
18709         // push current tail of game on stack
18710         savedResult[storedGames] = gameInfo.result;
18711         savedDetails[storedGames] = gameInfo.resultDetails;
18712         gameInfo.resultDetails = NULL;
18713         savedFirst[storedGames] = firstMove;
18714         savedLast [storedGames] = lastMove;
18715         savedFramePtr[storedGames] = framePtr;
18716         framePtr -= nrMoves; // reserve space for the boards
18717         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18718             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18719             for(j=0; j<MOVE_LEN; j++)
18720                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18721             for(j=0; j<2*MOVE_LEN; j++)
18722                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18723             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18724             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18725             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18726             pvInfoList[firstMove+i-1].depth = 0;
18727             commentList[framePtr+i] = commentList[firstMove+i];
18728             commentList[firstMove+i] = NULL;
18729         }
18730
18731         storedGames++;
18732         forwardMostMove = firstMove; // truncate game so we can start variation
18733 }
18734
18735 void
18736 PushTail (int firstMove, int lastMove)
18737 {
18738         if(appData.icsActive) { // only in local mode
18739                 forwardMostMove = currentMove; // mimic old ICS behavior
18740                 return;
18741         }
18742         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18743
18744         PushInner(firstMove, lastMove);
18745         if(storedGames == 1) GreyRevert(FALSE);
18746         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18747 }
18748
18749 void
18750 PopInner (Boolean annotate)
18751 {
18752         int i, j, nrMoves;
18753         char buf[8000], moveBuf[20];
18754
18755         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18756         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18757         nrMoves = savedLast[storedGames] - currentMove;
18758         if(annotate) {
18759                 int cnt = 10;
18760                 if(!WhiteOnMove(currentMove))
18761                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18762                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18763                 for(i=currentMove; i<forwardMostMove; i++) {
18764                         if(WhiteOnMove(i))
18765                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18766                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18767                         strcat(buf, moveBuf);
18768                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18769                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18770                 }
18771                 strcat(buf, ")");
18772         }
18773         for(i=1; i<=nrMoves; i++) { // copy last variation back
18774             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18775             for(j=0; j<MOVE_LEN; j++)
18776                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18777             for(j=0; j<2*MOVE_LEN; j++)
18778                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18779             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18780             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18781             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18782             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18783             commentList[currentMove+i] = commentList[framePtr+i];
18784             commentList[framePtr+i] = NULL;
18785         }
18786         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18787         framePtr = savedFramePtr[storedGames];
18788         gameInfo.result = savedResult[storedGames];
18789         if(gameInfo.resultDetails != NULL) {
18790             free(gameInfo.resultDetails);
18791       }
18792         gameInfo.resultDetails = savedDetails[storedGames];
18793         forwardMostMove = currentMove + nrMoves;
18794 }
18795
18796 Boolean
18797 PopTail (Boolean annotate)
18798 {
18799         if(appData.icsActive) return FALSE; // only in local mode
18800         if(!storedGames) return FALSE; // sanity
18801         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18802
18803         PopInner(annotate);
18804         if(currentMove < forwardMostMove) ForwardEvent(); else
18805         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18806
18807         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18808         return TRUE;
18809 }
18810
18811 void
18812 CleanupTail ()
18813 {       // remove all shelved variations
18814         int i;
18815         for(i=0; i<storedGames; i++) {
18816             if(savedDetails[i])
18817                 free(savedDetails[i]);
18818             savedDetails[i] = NULL;
18819         }
18820         for(i=framePtr; i<MAX_MOVES; i++) {
18821                 if(commentList[i]) free(commentList[i]);
18822                 commentList[i] = NULL;
18823         }
18824         framePtr = MAX_MOVES-1;
18825         storedGames = 0;
18826 }
18827
18828 void
18829 LoadVariation (int index, char *text)
18830 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18831         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18832         int level = 0, move;
18833
18834         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18835         // first find outermost bracketing variation
18836         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18837             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18838                 if(*p == '{') wait = '}'; else
18839                 if(*p == '[') wait = ']'; else
18840                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18841                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18842             }
18843             if(*p == wait) wait = NULLCHAR; // closing ]} found
18844             p++;
18845         }
18846         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18847         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18848         end[1] = NULLCHAR; // clip off comment beyond variation
18849         ToNrEvent(currentMove-1);
18850         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18851         // kludge: use ParsePV() to append variation to game
18852         move = currentMove;
18853         ParsePV(start, TRUE, TRUE);
18854         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18855         ClearPremoveHighlights();
18856         CommentPopDown();
18857         ToNrEvent(currentMove+1);
18858 }
18859
18860 void
18861 LoadTheme ()
18862 {
18863     char *p, *q, buf[MSG_SIZ];
18864     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18865         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18866         ParseArgsFromString(buf);
18867         ActivateTheme(TRUE); // also redo colors
18868         return;
18869     }
18870     p = nickName;
18871     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18872     {
18873         int len;
18874         q = appData.themeNames;
18875         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18876       if(appData.useBitmaps) {
18877         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18878                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18879                 appData.liteBackTextureMode,
18880                 appData.darkBackTextureMode );
18881       } else {
18882         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18883                 Col2Text(2),   // lightSquareColor
18884                 Col2Text(3) ); // darkSquareColor
18885       }
18886       if(appData.useBorder) {
18887         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18888                 appData.border);
18889       } else {
18890         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18891       }
18892       if(appData.useFont) {
18893         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18894                 appData.renderPiecesWithFont,
18895                 appData.fontToPieceTable,
18896                 Col2Text(9),    // appData.fontBackColorWhite
18897                 Col2Text(10) ); // appData.fontForeColorBlack
18898       } else {
18899         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18900                 appData.pieceDirectory);
18901         if(!appData.pieceDirectory[0])
18902           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18903                 Col2Text(0),   // whitePieceColor
18904                 Col2Text(1) ); // blackPieceColor
18905       }
18906       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18907                 Col2Text(4),   // highlightSquareColor
18908                 Col2Text(5) ); // premoveHighlightColor
18909         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18910         if(insert != q) insert[-1] = NULLCHAR;
18911         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18912         if(q)   free(q);
18913     }
18914     ActivateTheme(FALSE);
18915 }