Allow setting of piece nicknames from pieceToChar string
[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)) - 2*(*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             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6031         }
6032         table[(int) WhiteKing]  = map[j++];
6033         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6034             char *p, c=0;
6035             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6036             i = WHITE_TO_BLACK ii;
6037             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6038             table[i+offs] = map[j++];
6039             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6040             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6041             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6042         }
6043         table[(int) BlackKing]  = map[j++];
6044
6045
6046         if(*escapes) { // set up promotion pairing
6047             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6048             // pieceToChar entirely filled, so we can look up specified partners
6049             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6050                 int c = table[i];
6051                 if(c == '^' || c == '-') { // has specified partner
6052                     int p;
6053                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6054                     if(c == '^') table[i] = '+';
6055                     if(p < EmptySquare) {
6056                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6057                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6058                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6059                     }
6060                 } else if(c == '*') {
6061                     table[i] = partner[i];
6062                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6063                 }
6064             }
6065         }
6066
6067         result = TRUE;
6068     }
6069
6070     return result;
6071 }
6072
6073 int
6074 SetCharTable (unsigned char *table, const char * map)
6075 {
6076     return SetCharTableEsc(table, map, "");
6077 }
6078
6079 void
6080 Prelude (Board board)
6081 {       // [HGM] superchess: random selection of exo-pieces
6082         int i, j, k; ChessSquare p;
6083         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6084
6085         GetPositionNumber(); // use FRC position number
6086
6087         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6088             SetCharTable(pieceToChar, appData.pieceToCharTable);
6089             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6090                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6091         }
6092
6093         j = seed%4;                 seed /= 4;
6094         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6095         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6096         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6097         j = seed%3 + (seed%3 >= j); seed /= 3;
6098         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6099         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6100         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6101         j = seed%3;                 seed /= 3;
6102         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6103         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6104         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6105         j = seed%2 + (seed%2 >= j); seed /= 2;
6106         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6107         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6108         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6109         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6110         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6111         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6112         put(board, exoPieces[0],    0, 0, ANY);
6113         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6114 }
6115
6116 void
6117 InitPosition (int redraw)
6118 {
6119     ChessSquare (* pieces)[BOARD_FILES];
6120     int i, j, pawnRow=1, pieceRows=1, overrule,
6121     oldx = gameInfo.boardWidth,
6122     oldy = gameInfo.boardHeight,
6123     oldh = gameInfo.holdingsWidth;
6124     static int oldv;
6125
6126     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6127
6128     /* [AS] Initialize pv info list [HGM] and game status */
6129     {
6130         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6131             pvInfoList[i].depth = 0;
6132             boards[i][EP_STATUS] = EP_NONE;
6133             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6134         }
6135
6136         initialRulePlies = 0; /* 50-move counter start */
6137
6138         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6139         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6140     }
6141
6142
6143     /* [HGM] logic here is completely changed. In stead of full positions */
6144     /* the initialized data only consist of the two backranks. The switch */
6145     /* selects which one we will use, which is than copied to the Board   */
6146     /* initialPosition, which for the rest is initialized by Pawns and    */
6147     /* empty squares. This initial position is then copied to boards[0],  */
6148     /* possibly after shuffling, so that it remains available.            */
6149
6150     gameInfo.holdingsWidth = 0; /* default board sizes */
6151     gameInfo.boardWidth    = 8;
6152     gameInfo.boardHeight   = 8;
6153     gameInfo.holdingsSize  = 0;
6154     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6155     for(i=0; i<BOARD_FILES-6; i++)
6156       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6157     initialPosition[EP_STATUS] = EP_NONE;
6158     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6159     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6160     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6161          SetCharTable(pieceNickName, appData.pieceNickNames);
6162     else SetCharTable(pieceNickName, "............");
6163     pieces = FIDEArray;
6164
6165     switch (gameInfo.variant) {
6166     case VariantFischeRandom:
6167       shuffleOpenings = TRUE;
6168       appData.fischerCastling = TRUE;
6169     default:
6170       break;
6171     case VariantShatranj:
6172       pieces = ShatranjArray;
6173       nrCastlingRights = 0;
6174       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6175       break;
6176     case VariantMakruk:
6177       pieces = makrukArray;
6178       nrCastlingRights = 0;
6179       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6180       break;
6181     case VariantASEAN:
6182       pieces = aseanArray;
6183       nrCastlingRights = 0;
6184       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6185       break;
6186     case VariantTwoKings:
6187       pieces = twoKingsArray;
6188       break;
6189     case VariantGrand:
6190       pieces = GrandArray;
6191       nrCastlingRights = 0;
6192       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6193       gameInfo.boardWidth = 10;
6194       gameInfo.boardHeight = 10;
6195       gameInfo.holdingsSize = 7;
6196       break;
6197     case VariantCapaRandom:
6198       shuffleOpenings = TRUE;
6199       appData.fischerCastling = TRUE;
6200     case VariantCapablanca:
6201       pieces = CapablancaArray;
6202       gameInfo.boardWidth = 10;
6203       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6204       break;
6205     case VariantGothic:
6206       pieces = GothicArray;
6207       gameInfo.boardWidth = 10;
6208       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6209       break;
6210     case VariantSChess:
6211       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6212       gameInfo.holdingsSize = 7;
6213       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6214       break;
6215     case VariantJanus:
6216       pieces = JanusArray;
6217       gameInfo.boardWidth = 10;
6218       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6219       nrCastlingRights = 6;
6220         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6221         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6222         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6223         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6224         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6225         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6226       break;
6227     case VariantFalcon:
6228       pieces = FalconArray;
6229       gameInfo.boardWidth = 10;
6230       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6231       break;
6232     case VariantXiangqi:
6233       pieces = XiangqiArray;
6234       gameInfo.boardWidth  = 9;
6235       gameInfo.boardHeight = 10;
6236       nrCastlingRights = 0;
6237       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6238       break;
6239     case VariantShogi:
6240       pieces = ShogiArray;
6241       gameInfo.boardWidth  = 9;
6242       gameInfo.boardHeight = 9;
6243       gameInfo.holdingsSize = 7;
6244       nrCastlingRights = 0;
6245       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6246       break;
6247     case VariantChu:
6248       pieces = ChuArray; pieceRows = 3;
6249       gameInfo.boardWidth  = 12;
6250       gameInfo.boardHeight = 12;
6251       nrCastlingRights = 0;
6252       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6253                                    "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6254       break;
6255     case VariantCourier:
6256       pieces = CourierArray;
6257       gameInfo.boardWidth  = 12;
6258       nrCastlingRights = 0;
6259       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6260       break;
6261     case VariantKnightmate:
6262       pieces = KnightmateArray;
6263       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6264       break;
6265     case VariantSpartan:
6266       pieces = SpartanArray;
6267       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6268       break;
6269     case VariantLion:
6270       pieces = lionArray;
6271       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6272       break;
6273     case VariantChuChess:
6274       pieces = ChuChessArray;
6275       gameInfo.boardWidth = 10;
6276       gameInfo.boardHeight = 10;
6277       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6278       break;
6279     case VariantFairy:
6280       pieces = fairyArray;
6281       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6282       break;
6283     case VariantGreat:
6284       pieces = GreatArray;
6285       gameInfo.boardWidth = 10;
6286       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6287       gameInfo.holdingsSize = 8;
6288       break;
6289     case VariantSuper:
6290       pieces = FIDEArray;
6291       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6292       gameInfo.holdingsSize = 8;
6293       startedFromSetupPosition = TRUE;
6294       break;
6295     case VariantCrazyhouse:
6296     case VariantBughouse:
6297       pieces = FIDEArray;
6298       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6299       gameInfo.holdingsSize = 5;
6300       break;
6301     case VariantWildCastle:
6302       pieces = FIDEArray;
6303       /* !!?shuffle with kings guaranteed to be on d or e file */
6304       shuffleOpenings = 1;
6305       break;
6306     case VariantNoCastle:
6307       pieces = FIDEArray;
6308       nrCastlingRights = 0;
6309       /* !!?unconstrained back-rank shuffle */
6310       shuffleOpenings = 1;
6311       break;
6312     }
6313
6314     overrule = 0;
6315     if(appData.NrFiles >= 0) {
6316         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6317         gameInfo.boardWidth = appData.NrFiles;
6318     }
6319     if(appData.NrRanks >= 0) {
6320         gameInfo.boardHeight = appData.NrRanks;
6321     }
6322     if(appData.holdingsSize >= 0) {
6323         i = appData.holdingsSize;
6324         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6325         gameInfo.holdingsSize = i;
6326     }
6327     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6328     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6329         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6330
6331     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6332     if(pawnRow < 1) pawnRow = 1;
6333     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6334        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6335     if(gameInfo.variant == VariantChu) pawnRow = 3;
6336
6337     /* User pieceToChar list overrules defaults */
6338     if(appData.pieceToCharTable != NULL)
6339         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6340
6341     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6342
6343         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6344             s = (ChessSquare) 0; /* account holding counts in guard band */
6345         for( i=0; i<BOARD_HEIGHT; i++ )
6346             initialPosition[i][j] = s;
6347
6348         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6349         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6350         initialPosition[pawnRow][j] = WhitePawn;
6351         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6352         if(gameInfo.variant == VariantXiangqi) {
6353             if(j&1) {
6354                 initialPosition[pawnRow][j] =
6355                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6356                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6357                    initialPosition[2][j] = WhiteCannon;
6358                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6359                 }
6360             }
6361         }
6362         if(gameInfo.variant == VariantChu) {
6363              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6364                initialPosition[pawnRow+1][j] = WhiteCobra,
6365                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6366              for(i=1; i<pieceRows; i++) {
6367                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6368                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6369              }
6370         }
6371         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6372             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6373                initialPosition[0][j] = WhiteRook;
6374                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6375             }
6376         }
6377         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6378     }
6379     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6380     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6381
6382             j=BOARD_LEFT+1;
6383             initialPosition[1][j] = WhiteBishop;
6384             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6385             j=BOARD_RGHT-2;
6386             initialPosition[1][j] = WhiteRook;
6387             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6388     }
6389
6390     if( nrCastlingRights == -1) {
6391         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6392         /*       This sets default castling rights from none to normal corners   */
6393         /* Variants with other castling rights must set them themselves above    */
6394         nrCastlingRights = 6;
6395
6396         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6397         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6398         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6399         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6400         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6401         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6402      }
6403
6404      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6405      if(gameInfo.variant == VariantGreat) { // promotion commoners
6406         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6407         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6408         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6409         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6410      }
6411      if( gameInfo.variant == VariantSChess ) {
6412       initialPosition[1][0] = BlackMarshall;
6413       initialPosition[2][0] = BlackAngel;
6414       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6415       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6416       initialPosition[1][1] = initialPosition[2][1] =
6417       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6418      }
6419   if (appData.debugMode) {
6420     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6421   }
6422     if(shuffleOpenings) {
6423         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6424         startedFromSetupPosition = TRUE;
6425     }
6426     if(startedFromPositionFile) {
6427       /* [HGM] loadPos: use PositionFile for every new game */
6428       CopyBoard(initialPosition, filePosition);
6429       for(i=0; i<nrCastlingRights; i++)
6430           initialRights[i] = filePosition[CASTLING][i];
6431       startedFromSetupPosition = TRUE;
6432     }
6433
6434     CopyBoard(boards[0], initialPosition);
6435
6436     if(oldx != gameInfo.boardWidth ||
6437        oldy != gameInfo.boardHeight ||
6438        oldv != gameInfo.variant ||
6439        oldh != gameInfo.holdingsWidth
6440                                          )
6441             InitDrawingSizes(-2 ,0);
6442
6443     oldv = gameInfo.variant;
6444     if (redraw)
6445       DrawPosition(TRUE, boards[currentMove]);
6446 }
6447
6448 void
6449 SendBoard (ChessProgramState *cps, int moveNum)
6450 {
6451     char message[MSG_SIZ];
6452
6453     if (cps->useSetboard) {
6454       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6455       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6456       SendToProgram(message, cps);
6457       free(fen);
6458
6459     } else {
6460       ChessSquare *bp;
6461       int i, j, left=0, right=BOARD_WIDTH;
6462       /* Kludge to set black to move, avoiding the troublesome and now
6463        * deprecated "black" command.
6464        */
6465       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6466         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6467
6468       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6469
6470       SendToProgram("edit\n", cps);
6471       SendToProgram("#\n", cps);
6472       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6473         bp = &boards[moveNum][i][left];
6474         for (j = left; j < right; j++, bp++) {
6475           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6476           if ((int) *bp < (int) BlackPawn) {
6477             if(j == BOARD_RGHT+1)
6478                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6479             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6480             if(message[0] == '+' || message[0] == '~') {
6481               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6482                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6483                         AAA + j, ONE + i - '0');
6484             }
6485             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6486                 message[1] = BOARD_RGHT   - 1 - j + '1';
6487                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6488             }
6489             SendToProgram(message, cps);
6490           }
6491         }
6492       }
6493
6494       SendToProgram("c\n", cps);
6495       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6496         bp = &boards[moveNum][i][left];
6497         for (j = left; j < right; j++, bp++) {
6498           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6499           if (((int) *bp != (int) EmptySquare)
6500               && ((int) *bp >= (int) BlackPawn)) {
6501             if(j == BOARD_LEFT-2)
6502                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6503             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6504                     AAA + j, ONE + i - '0');
6505             if(message[0] == '+' || message[0] == '~') {
6506               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6507                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6508                         AAA + j, ONE + i - '0');
6509             }
6510             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6511                 message[1] = BOARD_RGHT   - 1 - j + '1';
6512                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6513             }
6514             SendToProgram(message, cps);
6515           }
6516         }
6517       }
6518
6519       SendToProgram(".\n", cps);
6520     }
6521     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6522 }
6523
6524 char exclusionHeader[MSG_SIZ];
6525 int exCnt, excludePtr;
6526 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6527 static Exclusion excluTab[200];
6528 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6529
6530 static void
6531 WriteMap (int s)
6532 {
6533     int j;
6534     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6535     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6536 }
6537
6538 static void
6539 ClearMap ()
6540 {
6541     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6542     excludePtr = 24; exCnt = 0;
6543     WriteMap(0);
6544 }
6545
6546 static void
6547 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6548 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6549     char buf[2*MOVE_LEN], *p;
6550     Exclusion *e = excluTab;
6551     int i;
6552     for(i=0; i<exCnt; i++)
6553         if(e[i].ff == fromX && e[i].fr == fromY &&
6554            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6555     if(i == exCnt) { // was not in exclude list; add it
6556         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6557         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6558             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6559             return; // abort
6560         }
6561         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6562         excludePtr++; e[i].mark = excludePtr++;
6563         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6564         exCnt++;
6565     }
6566     exclusionHeader[e[i].mark] = state;
6567 }
6568
6569 static int
6570 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6571 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6572     char buf[MSG_SIZ];
6573     int j, k;
6574     ChessMove moveType;
6575     if((signed char)promoChar == -1) { // kludge to indicate best move
6576         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6577             return 1; // if unparsable, abort
6578     }
6579     // update exclusion map (resolving toggle by consulting existing state)
6580     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6581     j = k%8; k >>= 3;
6582     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6583     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6584          excludeMap[k] |=   1<<j;
6585     else excludeMap[k] &= ~(1<<j);
6586     // update header
6587     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6588     // inform engine
6589     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6590     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6591     SendToBoth(buf);
6592     return (state == '+');
6593 }
6594
6595 static void
6596 ExcludeClick (int index)
6597 {
6598     int i, j;
6599     Exclusion *e = excluTab;
6600     if(index < 25) { // none, best or tail clicked
6601         if(index < 13) { // none: include all
6602             WriteMap(0); // clear map
6603             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6604             SendToBoth("include all\n"); // and inform engine
6605         } else if(index > 18) { // tail
6606             if(exclusionHeader[19] == '-') { // tail was excluded
6607                 SendToBoth("include all\n");
6608                 WriteMap(0); // clear map completely
6609                 // now re-exclude selected moves
6610                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6611                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6612             } else { // tail was included or in mixed state
6613                 SendToBoth("exclude all\n");
6614                 WriteMap(0xFF); // fill map completely
6615                 // now re-include selected moves
6616                 j = 0; // count them
6617                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6618                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6619                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6620             }
6621         } else { // best
6622             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6623         }
6624     } else {
6625         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6626             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6627             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6628             break;
6629         }
6630     }
6631 }
6632
6633 ChessSquare
6634 DefaultPromoChoice (int white)
6635 {
6636     ChessSquare result;
6637     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6638        gameInfo.variant == VariantMakruk)
6639         result = WhiteFerz; // no choice
6640     else if(gameInfo.variant == VariantASEAN)
6641         result = WhiteRook; // no choice
6642     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6643         result= WhiteKing; // in Suicide Q is the last thing we want
6644     else if(gameInfo.variant == VariantSpartan)
6645         result = white ? WhiteQueen : WhiteAngel;
6646     else result = WhiteQueen;
6647     if(!white) result = WHITE_TO_BLACK result;
6648     return result;
6649 }
6650
6651 static int autoQueen; // [HGM] oneclick
6652
6653 int
6654 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6655 {
6656     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6657     /* [HGM] add Shogi promotions */
6658     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6659     ChessSquare piece, partner;
6660     ChessMove moveType;
6661     Boolean premove;
6662
6663     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6664     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6665
6666     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6667       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6668         return FALSE;
6669
6670     piece = boards[currentMove][fromY][fromX];
6671     if(gameInfo.variant == VariantChu) {
6672         promotionZoneSize = BOARD_HEIGHT/3;
6673         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6674     } else if(gameInfo.variant == VariantShogi) {
6675         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6676         highestPromotingPiece = (int)WhiteAlfil;
6677     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6678         promotionZoneSize = 3;
6679     }
6680
6681     // Treat Lance as Pawn when it is not representing Amazon or Lance
6682     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6683         if(piece == WhiteLance) piece = WhitePawn; else
6684         if(piece == BlackLance) piece = BlackPawn;
6685     }
6686
6687     // next weed out all moves that do not touch the promotion zone at all
6688     if((int)piece >= BlackPawn) {
6689         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6690              return FALSE;
6691         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6692         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6693     } else {
6694         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6695            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6696         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6697              return FALSE;
6698     }
6699
6700     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6701
6702     // weed out mandatory Shogi promotions
6703     if(gameInfo.variant == VariantShogi) {
6704         if(piece >= BlackPawn) {
6705             if(toY == 0 && piece == BlackPawn ||
6706                toY == 0 && piece == BlackQueen ||
6707                toY <= 1 && piece == BlackKnight) {
6708                 *promoChoice = '+';
6709                 return FALSE;
6710             }
6711         } else {
6712             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6713                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6714                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6715                 *promoChoice = '+';
6716                 return FALSE;
6717             }
6718         }
6719     }
6720
6721     // weed out obviously illegal Pawn moves
6722     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6723         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6724         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6725         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6726         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6727         // note we are not allowed to test for valid (non-)capture, due to premove
6728     }
6729
6730     // we either have a choice what to promote to, or (in Shogi) whether to promote
6731     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6732        gameInfo.variant == VariantMakruk) {
6733         ChessSquare p=BlackFerz;  // no choice
6734         while(p < EmptySquare) {  //but make sure we use piece that exists
6735             *promoChoice = PieceToChar(p++);
6736             if(*promoChoice != '.') break;
6737         }
6738         return FALSE;
6739     }
6740     // no sense asking what we must promote to if it is going to explode...
6741     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6742         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6743         return FALSE;
6744     }
6745     // give caller the default choice even if we will not make it
6746     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6747     partner = piece; // pieces can promote if the pieceToCharTable says so
6748     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6749     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6750     if(        sweepSelect && gameInfo.variant != VariantGreat
6751                            && gameInfo.variant != VariantGrand
6752                            && gameInfo.variant != VariantSuper) return FALSE;
6753     if(autoQueen) return FALSE; // predetermined
6754
6755     // suppress promotion popup on illegal moves that are not premoves
6756     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6757               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6758     if(appData.testLegality && !premove) {
6759         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6760                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6761         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6762         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6763             return FALSE;
6764     }
6765
6766     return TRUE;
6767 }
6768
6769 int
6770 InPalace (int row, int column)
6771 {   /* [HGM] for Xiangqi */
6772     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6773          column < (BOARD_WIDTH + 4)/2 &&
6774          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6775     return FALSE;
6776 }
6777
6778 int
6779 PieceForSquare (int x, int y)
6780 {
6781   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6782      return -1;
6783   else
6784      return boards[currentMove][y][x];
6785 }
6786
6787 int
6788 OKToStartUserMove (int x, int y)
6789 {
6790     ChessSquare from_piece;
6791     int white_piece;
6792
6793     if (matchMode) return FALSE;
6794     if (gameMode == EditPosition) return TRUE;
6795
6796     if (x >= 0 && y >= 0)
6797       from_piece = boards[currentMove][y][x];
6798     else
6799       from_piece = EmptySquare;
6800
6801     if (from_piece == EmptySquare) return FALSE;
6802
6803     white_piece = (int)from_piece >= (int)WhitePawn &&
6804       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6805
6806     switch (gameMode) {
6807       case AnalyzeFile:
6808       case TwoMachinesPlay:
6809       case EndOfGame:
6810         return FALSE;
6811
6812       case IcsObserving:
6813       case IcsIdle:
6814         return FALSE;
6815
6816       case MachinePlaysWhite:
6817       case IcsPlayingBlack:
6818         if (appData.zippyPlay) return FALSE;
6819         if (white_piece) {
6820             DisplayMoveError(_("You are playing Black"));
6821             return FALSE;
6822         }
6823         break;
6824
6825       case MachinePlaysBlack:
6826       case IcsPlayingWhite:
6827         if (appData.zippyPlay) return FALSE;
6828         if (!white_piece) {
6829             DisplayMoveError(_("You are playing White"));
6830             return FALSE;
6831         }
6832         break;
6833
6834       case PlayFromGameFile:
6835             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6836       case EditGame:
6837         if (!white_piece && WhiteOnMove(currentMove)) {
6838             DisplayMoveError(_("It is White's turn"));
6839             return FALSE;
6840         }
6841         if (white_piece && !WhiteOnMove(currentMove)) {
6842             DisplayMoveError(_("It is Black's turn"));
6843             return FALSE;
6844         }
6845         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6846             /* Editing correspondence game history */
6847             /* Could disallow this or prompt for confirmation */
6848             cmailOldMove = -1;
6849         }
6850         break;
6851
6852       case BeginningOfGame:
6853         if (appData.icsActive) return FALSE;
6854         if (!appData.noChessProgram) {
6855             if (!white_piece) {
6856                 DisplayMoveError(_("You are playing White"));
6857                 return FALSE;
6858             }
6859         }
6860         break;
6861
6862       case Training:
6863         if (!white_piece && WhiteOnMove(currentMove)) {
6864             DisplayMoveError(_("It is White's turn"));
6865             return FALSE;
6866         }
6867         if (white_piece && !WhiteOnMove(currentMove)) {
6868             DisplayMoveError(_("It is Black's turn"));
6869             return FALSE;
6870         }
6871         break;
6872
6873       default:
6874       case IcsExamining:
6875         break;
6876     }
6877     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6878         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6879         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6880         && gameMode != AnalyzeFile && gameMode != Training) {
6881         DisplayMoveError(_("Displayed position is not current"));
6882         return FALSE;
6883     }
6884     return TRUE;
6885 }
6886
6887 Boolean
6888 OnlyMove (int *x, int *y, Boolean captures)
6889 {
6890     DisambiguateClosure cl;
6891     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6892     switch(gameMode) {
6893       case MachinePlaysBlack:
6894       case IcsPlayingWhite:
6895       case BeginningOfGame:
6896         if(!WhiteOnMove(currentMove)) return FALSE;
6897         break;
6898       case MachinePlaysWhite:
6899       case IcsPlayingBlack:
6900         if(WhiteOnMove(currentMove)) return FALSE;
6901         break;
6902       case EditGame:
6903         break;
6904       default:
6905         return FALSE;
6906     }
6907     cl.pieceIn = EmptySquare;
6908     cl.rfIn = *y;
6909     cl.ffIn = *x;
6910     cl.rtIn = -1;
6911     cl.ftIn = -1;
6912     cl.promoCharIn = NULLCHAR;
6913     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6914     if( cl.kind == NormalMove ||
6915         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6916         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6917         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6918       fromX = cl.ff;
6919       fromY = cl.rf;
6920       *x = cl.ft;
6921       *y = cl.rt;
6922       return TRUE;
6923     }
6924     if(cl.kind != ImpossibleMove) return FALSE;
6925     cl.pieceIn = EmptySquare;
6926     cl.rfIn = -1;
6927     cl.ffIn = -1;
6928     cl.rtIn = *y;
6929     cl.ftIn = *x;
6930     cl.promoCharIn = NULLCHAR;
6931     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6932     if( cl.kind == NormalMove ||
6933         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6934         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6935         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6936       fromX = cl.ff;
6937       fromY = cl.rf;
6938       *x = cl.ft;
6939       *y = cl.rt;
6940       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6941       return TRUE;
6942     }
6943     return FALSE;
6944 }
6945
6946 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6947 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6948 int lastLoadGameUseList = FALSE;
6949 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6950 ChessMove lastLoadGameStart = EndOfFile;
6951 int doubleClick;
6952 Boolean addToBookFlag;
6953
6954 void
6955 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6956 {
6957     ChessMove moveType;
6958     ChessSquare pup;
6959     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6960
6961     /* Check if the user is playing in turn.  This is complicated because we
6962        let the user "pick up" a piece before it is his turn.  So the piece he
6963        tried to pick up may have been captured by the time he puts it down!
6964        Therefore we use the color the user is supposed to be playing in this
6965        test, not the color of the piece that is currently on the starting
6966        square---except in EditGame mode, where the user is playing both
6967        sides; fortunately there the capture race can't happen.  (It can
6968        now happen in IcsExamining mode, but that's just too bad.  The user
6969        will get a somewhat confusing message in that case.)
6970        */
6971
6972     switch (gameMode) {
6973       case AnalyzeFile:
6974       case TwoMachinesPlay:
6975       case EndOfGame:
6976       case IcsObserving:
6977       case IcsIdle:
6978         /* We switched into a game mode where moves are not accepted,
6979            perhaps while the mouse button was down. */
6980         return;
6981
6982       case MachinePlaysWhite:
6983         /* User is moving for Black */
6984         if (WhiteOnMove(currentMove)) {
6985             DisplayMoveError(_("It is White's turn"));
6986             return;
6987         }
6988         break;
6989
6990       case MachinePlaysBlack:
6991         /* User is moving for White */
6992         if (!WhiteOnMove(currentMove)) {
6993             DisplayMoveError(_("It is Black's turn"));
6994             return;
6995         }
6996         break;
6997
6998       case PlayFromGameFile:
6999             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7000       case EditGame:
7001       case IcsExamining:
7002       case BeginningOfGame:
7003       case AnalyzeMode:
7004       case Training:
7005         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7006         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7007             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7008             /* User is moving for Black */
7009             if (WhiteOnMove(currentMove)) {
7010                 DisplayMoveError(_("It is White's turn"));
7011                 return;
7012             }
7013         } else {
7014             /* User is moving for White */
7015             if (!WhiteOnMove(currentMove)) {
7016                 DisplayMoveError(_("It is Black's turn"));
7017                 return;
7018             }
7019         }
7020         break;
7021
7022       case IcsPlayingBlack:
7023         /* User is moving for Black */
7024         if (WhiteOnMove(currentMove)) {
7025             if (!appData.premove) {
7026                 DisplayMoveError(_("It is White's turn"));
7027             } else if (toX >= 0 && toY >= 0) {
7028                 premoveToX = toX;
7029                 premoveToY = toY;
7030                 premoveFromX = fromX;
7031                 premoveFromY = fromY;
7032                 premovePromoChar = promoChar;
7033                 gotPremove = 1;
7034                 if (appData.debugMode)
7035                     fprintf(debugFP, "Got premove: fromX %d,"
7036                             "fromY %d, toX %d, toY %d\n",
7037                             fromX, fromY, toX, toY);
7038             }
7039             return;
7040         }
7041         break;
7042
7043       case IcsPlayingWhite:
7044         /* User is moving for White */
7045         if (!WhiteOnMove(currentMove)) {
7046             if (!appData.premove) {
7047                 DisplayMoveError(_("It is Black's turn"));
7048             } else if (toX >= 0 && toY >= 0) {
7049                 premoveToX = toX;
7050                 premoveToY = toY;
7051                 premoveFromX = fromX;
7052                 premoveFromY = fromY;
7053                 premovePromoChar = promoChar;
7054                 gotPremove = 1;
7055                 if (appData.debugMode)
7056                     fprintf(debugFP, "Got premove: fromX %d,"
7057                             "fromY %d, toX %d, toY %d\n",
7058                             fromX, fromY, toX, toY);
7059             }
7060             return;
7061         }
7062         break;
7063
7064       default:
7065         break;
7066
7067       case EditPosition:
7068         /* EditPosition, empty square, or different color piece;
7069            click-click move is possible */
7070         if (toX == -2 || toY == -2) {
7071             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7072             DrawPosition(FALSE, boards[currentMove]);
7073             return;
7074         } else if (toX >= 0 && toY >= 0) {
7075             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7076                 ChessSquare p = boards[0][rf][ff];
7077                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7078                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); 
7079             }
7080             boards[0][toY][toX] = boards[0][fromY][fromX];
7081             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7082                 if(boards[0][fromY][0] != EmptySquare) {
7083                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7084                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7085                 }
7086             } else
7087             if(fromX == BOARD_RGHT+1) {
7088                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7089                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7090                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7091                 }
7092             } else
7093             boards[0][fromY][fromX] = gatingPiece;
7094             ClearHighlights();
7095             DrawPosition(FALSE, boards[currentMove]);
7096             return;
7097         }
7098         return;
7099     }
7100
7101     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7102     pup = boards[currentMove][toY][toX];
7103
7104     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7105     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7106          if( pup != EmptySquare ) return;
7107          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7108            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7109                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7110            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7111            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7112            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7113            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7114          fromY = DROP_RANK;
7115     }
7116
7117     /* [HGM] always test for legality, to get promotion info */
7118     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7119                                          fromY, fromX, toY, toX, promoChar);
7120
7121     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7122
7123     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7124
7125     /* [HGM] but possibly ignore an IllegalMove result */
7126     if (appData.testLegality) {
7127         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7128             DisplayMoveError(_("Illegal move"));
7129             return;
7130         }
7131     }
7132
7133     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7134         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7135              ClearPremoveHighlights(); // was included
7136         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7137         return;
7138     }
7139
7140     if(addToBookFlag) { // adding moves to book
7141         char buf[MSG_SIZ], move[MSG_SIZ];
7142         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7143         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');
7144         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7145         AddBookMove(buf);
7146         addToBookFlag = FALSE;
7147         ClearHighlights();
7148         return;
7149     }
7150
7151     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7152 }
7153
7154 /* Common tail of UserMoveEvent and DropMenuEvent */
7155 int
7156 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7157 {
7158     char *bookHit = 0;
7159
7160     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7161         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7162         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7163         if(WhiteOnMove(currentMove)) {
7164             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7165         } else {
7166             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7167         }
7168     }
7169
7170     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7171        move type in caller when we know the move is a legal promotion */
7172     if(moveType == NormalMove && promoChar)
7173         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7174
7175     /* [HGM] <popupFix> The following if has been moved here from
7176        UserMoveEvent(). Because it seemed to belong here (why not allow
7177        piece drops in training games?), and because it can only be
7178        performed after it is known to what we promote. */
7179     if (gameMode == Training) {
7180       /* compare the move played on the board to the next move in the
7181        * game. If they match, display the move and the opponent's response.
7182        * If they don't match, display an error message.
7183        */
7184       int saveAnimate;
7185       Board testBoard;
7186       CopyBoard(testBoard, boards[currentMove]);
7187       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7188
7189       if (CompareBoards(testBoard, boards[currentMove+1])) {
7190         ForwardInner(currentMove+1);
7191
7192         /* Autoplay the opponent's response.
7193          * if appData.animate was TRUE when Training mode was entered,
7194          * the response will be animated.
7195          */
7196         saveAnimate = appData.animate;
7197         appData.animate = animateTraining;
7198         ForwardInner(currentMove+1);
7199         appData.animate = saveAnimate;
7200
7201         /* check for the end of the game */
7202         if (currentMove >= forwardMostMove) {
7203           gameMode = PlayFromGameFile;
7204           ModeHighlight();
7205           SetTrainingModeOff();
7206           DisplayInformation(_("End of game"));
7207         }
7208       } else {
7209         DisplayError(_("Incorrect move"), 0);
7210       }
7211       return 1;
7212     }
7213
7214   /* Ok, now we know that the move is good, so we can kill
7215      the previous line in Analysis Mode */
7216   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7217                                 && currentMove < forwardMostMove) {
7218     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7219     else forwardMostMove = currentMove;
7220   }
7221
7222   ClearMap();
7223
7224   /* If we need the chess program but it's dead, restart it */
7225   ResurrectChessProgram();
7226
7227   /* A user move restarts a paused game*/
7228   if (pausing)
7229     PauseEvent();
7230
7231   thinkOutput[0] = NULLCHAR;
7232
7233   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7234
7235   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7236     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7237     return 1;
7238   }
7239
7240   if (gameMode == BeginningOfGame) {
7241     if (appData.noChessProgram) {
7242       gameMode = EditGame;
7243       SetGameInfo();
7244     } else {
7245       char buf[MSG_SIZ];
7246       gameMode = MachinePlaysBlack;
7247       StartClocks();
7248       SetGameInfo();
7249       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7250       DisplayTitle(buf);
7251       if (first.sendName) {
7252         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7253         SendToProgram(buf, &first);
7254       }
7255       StartClocks();
7256     }
7257     ModeHighlight();
7258   }
7259
7260   /* Relay move to ICS or chess engine */
7261   if (appData.icsActive) {
7262     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7263         gameMode == IcsExamining) {
7264       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7265         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7266         SendToICS("draw ");
7267         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7268       }
7269       // also send plain move, in case ICS does not understand atomic claims
7270       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7271       ics_user_moved = 1;
7272     }
7273   } else {
7274     if (first.sendTime && (gameMode == BeginningOfGame ||
7275                            gameMode == MachinePlaysWhite ||
7276                            gameMode == MachinePlaysBlack)) {
7277       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7278     }
7279     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7280          // [HGM] book: if program might be playing, let it use book
7281         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7282         first.maybeThinking = TRUE;
7283     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7284         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7285         SendBoard(&first, currentMove+1);
7286         if(second.analyzing) {
7287             if(!second.useSetboard) SendToProgram("undo\n", &second);
7288             SendBoard(&second, currentMove+1);
7289         }
7290     } else {
7291         SendMoveToProgram(forwardMostMove-1, &first);
7292         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7293     }
7294     if (currentMove == cmailOldMove + 1) {
7295       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7296     }
7297   }
7298
7299   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7300
7301   switch (gameMode) {
7302   case EditGame:
7303     if(appData.testLegality)
7304     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7305     case MT_NONE:
7306     case MT_CHECK:
7307       break;
7308     case MT_CHECKMATE:
7309     case MT_STAINMATE:
7310       if (WhiteOnMove(currentMove)) {
7311         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7312       } else {
7313         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7314       }
7315       break;
7316     case MT_STALEMATE:
7317       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7318       break;
7319     }
7320     break;
7321
7322   case MachinePlaysBlack:
7323   case MachinePlaysWhite:
7324     /* disable certain menu options while machine is thinking */
7325     SetMachineThinkingEnables();
7326     break;
7327
7328   default:
7329     break;
7330   }
7331
7332   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7333   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7334
7335   if(bookHit) { // [HGM] book: simulate book reply
7336         static char bookMove[MSG_SIZ]; // a bit generous?
7337
7338         programStats.nodes = programStats.depth = programStats.time =
7339         programStats.score = programStats.got_only_move = 0;
7340         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7341
7342         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7343         strcat(bookMove, bookHit);
7344         HandleMachineMove(bookMove, &first);
7345   }
7346   return 1;
7347 }
7348
7349 void
7350 MarkByFEN(char *fen)
7351 {
7352         int r, f;
7353         if(!appData.markers || !appData.highlightDragging) return;
7354         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7355         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7356         while(*fen) {
7357             int s = 0;
7358             marker[r][f] = 0;
7359             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7360             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7361             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7362             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7363             if(*fen == 'T') marker[r][f++] = 0; else
7364             if(*fen == 'Y') marker[r][f++] = 1; else
7365             if(*fen == 'G') marker[r][f++] = 3; else
7366             if(*fen == 'B') marker[r][f++] = 4; else
7367             if(*fen == 'C') marker[r][f++] = 5; else
7368             if(*fen == 'M') marker[r][f++] = 6; else
7369             if(*fen == 'W') marker[r][f++] = 7; else
7370             if(*fen == 'D') marker[r][f++] = 8; else
7371             if(*fen == 'R') marker[r][f++] = 2; else {
7372                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7373               f += s; fen -= s>0;
7374             }
7375             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7376             if(r < 0) break;
7377             fen++;
7378         }
7379         DrawPosition(TRUE, NULL);
7380 }
7381
7382 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7383
7384 void
7385 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7386 {
7387     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7388     Markers *m = (Markers *) closure;
7389     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7390         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7391                          || kind == WhiteCapturesEnPassant
7392                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 3;
7393     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7394 }
7395
7396 static int hoverSavedValid;
7397
7398 void
7399 MarkTargetSquares (int clear)
7400 {
7401   int x, y, sum=0;
7402   if(clear) { // no reason to ever suppress clearing
7403     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7404     hoverSavedValid = 0;
7405     if(!sum) return; // nothing was cleared,no redraw needed
7406   } else {
7407     int capt = 0;
7408     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7409        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7410     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7411     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7412       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7413       if(capt)
7414       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7415     }
7416   }
7417   DrawPosition(FALSE, NULL);
7418 }
7419
7420 int
7421 Explode (Board board, int fromX, int fromY, int toX, int toY)
7422 {
7423     if(gameInfo.variant == VariantAtomic &&
7424        (board[toY][toX] != EmptySquare ||                     // capture?
7425         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7426                          board[fromY][fromX] == BlackPawn   )
7427       )) {
7428         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7429         return TRUE;
7430     }
7431     return FALSE;
7432 }
7433
7434 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7435
7436 int
7437 CanPromote (ChessSquare piece, int y)
7438 {
7439         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7440         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7441         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7442         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7443            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7444            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7445          gameInfo.variant == VariantMakruk) return FALSE;
7446         return (piece == BlackPawn && y <= zone ||
7447                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7448                 piece == BlackLance && y <= zone ||
7449                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7450 }
7451
7452 void
7453 HoverEvent (int xPix, int yPix, int x, int y)
7454 {
7455         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7456         int r, f;
7457         if(!first.highlight) return;
7458         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7459         if(x == oldX && y == oldY) return; // only do something if we enter new square
7460         oldFromX = fromX; oldFromY = fromY;
7461         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7462           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7463             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7464           hoverSavedValid = 1;
7465         } else if(oldX != x || oldY != y) {
7466           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7467           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7468           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7469             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7470           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7471             char buf[MSG_SIZ];
7472             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7473             SendToProgram(buf, &first);
7474           }
7475           oldX = x; oldY = y;
7476 //        SetHighlights(fromX, fromY, x, y);
7477         }
7478 }
7479
7480 void ReportClick(char *action, int x, int y)
7481 {
7482         char buf[MSG_SIZ]; // Inform engine of what user does
7483         int r, f;
7484         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7485           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7486             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7487         if(!first.highlight || gameMode == EditPosition) return;
7488         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7489         SendToProgram(buf, &first);
7490 }
7491
7492 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7493
7494 void
7495 LeftClick (ClickType clickType, int xPix, int yPix)
7496 {
7497     int x, y;
7498     Boolean saveAnimate;
7499     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7500     char promoChoice = NULLCHAR;
7501     ChessSquare piece;
7502     static TimeMark lastClickTime, prevClickTime;
7503
7504     x = EventToSquare(xPix, BOARD_WIDTH);
7505     y = EventToSquare(yPix, BOARD_HEIGHT);
7506     if (!flipView && y >= 0) {
7507         y = BOARD_HEIGHT - 1 - y;
7508     }
7509     if (flipView && x >= 0) {
7510         x = BOARD_WIDTH - 1 - x;
7511     }
7512
7513     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7514         static int dummy;
7515         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7516         right = TRUE;
7517         return;
7518     }
7519
7520     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7521
7522     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7523
7524     if (clickType == Press) ErrorPopDown();
7525     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7526
7527     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7528         defaultPromoChoice = promoSweep;
7529         promoSweep = EmptySquare;   // terminate sweep
7530         promoDefaultAltered = TRUE;
7531         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7532     }
7533
7534     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7535         if(clickType == Release) return; // ignore upclick of click-click destination
7536         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7537         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7538         if(gameInfo.holdingsWidth &&
7539                 (WhiteOnMove(currentMove)
7540                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7541                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7542             // click in right holdings, for determining promotion piece
7543             ChessSquare p = boards[currentMove][y][x];
7544             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7545             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7546             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7547                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7548                 fromX = fromY = -1;
7549                 return;
7550             }
7551         }
7552         DrawPosition(FALSE, boards[currentMove]);
7553         return;
7554     }
7555
7556     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7557     if(clickType == Press
7558             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7559               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7560               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7561         return;
7562
7563     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7564         // could be static click on premove from-square: abort premove
7565         gotPremove = 0;
7566         ClearPremoveHighlights();
7567     }
7568
7569     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7570         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7571
7572     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7573         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7574                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7575         defaultPromoChoice = DefaultPromoChoice(side);
7576     }
7577
7578     autoQueen = appData.alwaysPromoteToQueen;
7579
7580     if (fromX == -1) {
7581       int originalY = y;
7582       gatingPiece = EmptySquare;
7583       if (clickType != Press) {
7584         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7585             DragPieceEnd(xPix, yPix); dragging = 0;
7586             DrawPosition(FALSE, NULL);
7587         }
7588         return;
7589       }
7590       doubleClick = FALSE;
7591       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7592         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7593       }
7594       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7595       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7596          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7597          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7598             /* First square */
7599             if (OKToStartUserMove(fromX, fromY)) {
7600                 second = 0;
7601                 ReportClick("lift", x, y);
7602                 MarkTargetSquares(0);
7603                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7604                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7605                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7606                     promoSweep = defaultPromoChoice;
7607                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7608                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7609                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7610                 }
7611                 if (appData.highlightDragging) {
7612                     SetHighlights(fromX, fromY, -1, -1);
7613                 } else {
7614                     ClearHighlights();
7615                 }
7616             } else fromX = fromY = -1;
7617             return;
7618         }
7619     }
7620
7621     /* fromX != -1 */
7622     if (clickType == Press && gameMode != EditPosition) {
7623         ChessSquare fromP;
7624         ChessSquare toP;
7625         int frc;
7626
7627         // ignore off-board to clicks
7628         if(y < 0 || x < 0) return;
7629
7630         /* Check if clicking again on the same color piece */
7631         fromP = boards[currentMove][fromY][fromX];
7632         toP = boards[currentMove][y][x];
7633         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7634         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7635             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7636            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7637              WhitePawn <= toP && toP <= WhiteKing &&
7638              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7639              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7640             (BlackPawn <= fromP && fromP <= BlackKing &&
7641              BlackPawn <= toP && toP <= BlackKing &&
7642              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7643              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7644             /* Clicked again on same color piece -- changed his mind */
7645             second = (x == fromX && y == fromY);
7646             killX = killY = -1;
7647             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7648                 second = FALSE; // first double-click rather than scond click
7649                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7650             }
7651             promoDefaultAltered = FALSE;
7652             MarkTargetSquares(1);
7653            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7654             if (appData.highlightDragging) {
7655                 SetHighlights(x, y, -1, -1);
7656             } else {
7657                 ClearHighlights();
7658             }
7659             if (OKToStartUserMove(x, y)) {
7660                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7661                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7662                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7663                  gatingPiece = boards[currentMove][fromY][fromX];
7664                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7665                 fromX = x;
7666                 fromY = y; dragging = 1;
7667                 if(!second) ReportClick("lift", x, y);
7668                 MarkTargetSquares(0);
7669                 DragPieceBegin(xPix, yPix, FALSE);
7670                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7671                     promoSweep = defaultPromoChoice;
7672                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7673                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7674                 }
7675             }
7676            }
7677            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7678            second = FALSE;
7679         }
7680         // ignore clicks on holdings
7681         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7682     }
7683
7684     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7685         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7686         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7687         return;
7688     }
7689
7690     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7691         DragPieceEnd(xPix, yPix); dragging = 0;
7692         if(clearFlag) {
7693             // a deferred attempt to click-click move an empty square on top of a piece
7694             boards[currentMove][y][x] = EmptySquare;
7695             ClearHighlights();
7696             DrawPosition(FALSE, boards[currentMove]);
7697             fromX = fromY = -1; clearFlag = 0;
7698             return;
7699         }
7700         if (appData.animateDragging) {
7701             /* Undo animation damage if any */
7702             DrawPosition(FALSE, NULL);
7703         }
7704         if (second) {
7705             /* Second up/down in same square; just abort move */
7706             second = 0;
7707             fromX = fromY = -1;
7708             gatingPiece = EmptySquare;
7709             MarkTargetSquares(1);
7710             ClearHighlights();
7711             gotPremove = 0;
7712             ClearPremoveHighlights();
7713         } else {
7714             /* First upclick in same square; start click-click mode */
7715             SetHighlights(x, y, -1, -1);
7716         }
7717         return;
7718     }
7719
7720     clearFlag = 0;
7721
7722     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7723        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7724         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7725         DisplayMessage(_("only marked squares are legal"),"");
7726         DrawPosition(TRUE, NULL);
7727         return; // ignore to-click
7728     }
7729
7730     /* we now have a different from- and (possibly off-board) to-square */
7731     /* Completed move */
7732     if(!sweepSelecting) {
7733         toX = x;
7734         toY = y;
7735     }
7736
7737     piece = boards[currentMove][fromY][fromX];
7738
7739     saveAnimate = appData.animate;
7740     if (clickType == Press) {
7741         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7742         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7743             // must be Edit Position mode with empty-square selected
7744             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7745             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7746             return;
7747         }
7748         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7749             return;
7750         }
7751         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7752             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7753         } else
7754         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7755         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7756           if(appData.sweepSelect) {
7757             promoSweep = defaultPromoChoice;
7758             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7759             selectFlag = 0; lastX = xPix; lastY = yPix;
7760             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7761             Sweep(0); // Pawn that is going to promote: preview promotion piece
7762             sweepSelecting = 1;
7763             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7764             MarkTargetSquares(1);
7765           }
7766           return; // promo popup appears on up-click
7767         }
7768         /* Finish clickclick move */
7769         if (appData.animate || appData.highlightLastMove) {
7770             SetHighlights(fromX, fromY, toX, toY);
7771         } else {
7772             ClearHighlights();
7773         }
7774     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7775         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7776         *promoRestrict = 0;
7777         if (appData.animate || appData.highlightLastMove) {
7778             SetHighlights(fromX, fromY, toX, toY);
7779         } else {
7780             ClearHighlights();
7781         }
7782     } else {
7783 #if 0
7784 // [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
7785         /* Finish drag move */
7786         if (appData.highlightLastMove) {
7787             SetHighlights(fromX, fromY, toX, toY);
7788         } else {
7789             ClearHighlights();
7790         }
7791 #endif
7792         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7793           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7794         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7795         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7796           dragging *= 2;            // flag button-less dragging if we are dragging
7797           MarkTargetSquares(1);
7798           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7799           else {
7800             kill2X = killX; kill2Y = killY;
7801             killX = x; killY = y;     //remeber this square as intermediate
7802             ReportClick("put", x, y); // and inform engine
7803             ReportClick("lift", x, y);
7804             MarkTargetSquares(0);
7805             return;
7806           }
7807         }
7808         DragPieceEnd(xPix, yPix); dragging = 0;
7809         /* Don't animate move and drag both */
7810         appData.animate = FALSE;
7811     }
7812
7813     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7814     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7815         ChessSquare piece = boards[currentMove][fromY][fromX];
7816         if(gameMode == EditPosition && piece != EmptySquare &&
7817            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7818             int n;
7819
7820             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7821                 n = PieceToNumber(piece - (int)BlackPawn);
7822                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7823                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7824                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7825             } else
7826             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7827                 n = PieceToNumber(piece);
7828                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7829                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7830                 boards[currentMove][n][BOARD_WIDTH-2]++;
7831             }
7832             boards[currentMove][fromY][fromX] = EmptySquare;
7833         }
7834         ClearHighlights();
7835         fromX = fromY = -1;
7836         MarkTargetSquares(1);
7837         DrawPosition(TRUE, boards[currentMove]);
7838         return;
7839     }
7840
7841     // off-board moves should not be highlighted
7842     if(x < 0 || y < 0) ClearHighlights();
7843     else ReportClick("put", x, y);
7844
7845     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7846
7847     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7848
7849     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7850         SetHighlights(fromX, fromY, toX, toY);
7851         MarkTargetSquares(1);
7852         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7853             // [HGM] super: promotion to captured piece selected from holdings
7854             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7855             promotionChoice = TRUE;
7856             // kludge follows to temporarily execute move on display, without promoting yet
7857             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7858             boards[currentMove][toY][toX] = p;
7859             DrawPosition(FALSE, boards[currentMove]);
7860             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7861             boards[currentMove][toY][toX] = q;
7862             DisplayMessage("Click in holdings to choose piece", "");
7863             return;
7864         }
7865         PromotionPopUp(promoChoice);
7866     } else {
7867         int oldMove = currentMove;
7868         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7869         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7870         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7871         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7872            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7873             DrawPosition(TRUE, boards[currentMove]);
7874         MarkTargetSquares(1);
7875         fromX = fromY = -1;
7876     }
7877     appData.animate = saveAnimate;
7878     if (appData.animate || appData.animateDragging) {
7879         /* Undo animation damage if needed */
7880         DrawPosition(FALSE, NULL);
7881     }
7882 }
7883
7884 int
7885 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7886 {   // front-end-free part taken out of PieceMenuPopup
7887     int whichMenu; int xSqr, ySqr;
7888
7889     if(seekGraphUp) { // [HGM] seekgraph
7890         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7891         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7892         return -2;
7893     }
7894
7895     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7896          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7897         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7898         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7899         if(action == Press)   {
7900             originalFlip = flipView;
7901             flipView = !flipView; // temporarily flip board to see game from partners perspective
7902             DrawPosition(TRUE, partnerBoard);
7903             DisplayMessage(partnerStatus, "");
7904             partnerUp = TRUE;
7905         } else if(action == Release) {
7906             flipView = originalFlip;
7907             DrawPosition(TRUE, boards[currentMove]);
7908             partnerUp = FALSE;
7909         }
7910         return -2;
7911     }
7912
7913     xSqr = EventToSquare(x, BOARD_WIDTH);
7914     ySqr = EventToSquare(y, BOARD_HEIGHT);
7915     if (action == Release) {
7916         if(pieceSweep != EmptySquare) {
7917             EditPositionMenuEvent(pieceSweep, toX, toY);
7918             pieceSweep = EmptySquare;
7919         } else UnLoadPV(); // [HGM] pv
7920     }
7921     if (action != Press) return -2; // return code to be ignored
7922     switch (gameMode) {
7923       case IcsExamining:
7924         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7925       case EditPosition:
7926         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7927         if (xSqr < 0 || ySqr < 0) return -1;
7928         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7929         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7930         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7931         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7932         NextPiece(0);
7933         return 2; // grab
7934       case IcsObserving:
7935         if(!appData.icsEngineAnalyze) return -1;
7936       case IcsPlayingWhite:
7937       case IcsPlayingBlack:
7938         if(!appData.zippyPlay) goto noZip;
7939       case AnalyzeMode:
7940       case AnalyzeFile:
7941       case MachinePlaysWhite:
7942       case MachinePlaysBlack:
7943       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7944         if (!appData.dropMenu) {
7945           LoadPV(x, y);
7946           return 2; // flag front-end to grab mouse events
7947         }
7948         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7949            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7950       case EditGame:
7951       noZip:
7952         if (xSqr < 0 || ySqr < 0) return -1;
7953         if (!appData.dropMenu || appData.testLegality &&
7954             gameInfo.variant != VariantBughouse &&
7955             gameInfo.variant != VariantCrazyhouse) return -1;
7956         whichMenu = 1; // drop menu
7957         break;
7958       default:
7959         return -1;
7960     }
7961
7962     if (((*fromX = xSqr) < 0) ||
7963         ((*fromY = ySqr) < 0)) {
7964         *fromX = *fromY = -1;
7965         return -1;
7966     }
7967     if (flipView)
7968       *fromX = BOARD_WIDTH - 1 - *fromX;
7969     else
7970       *fromY = BOARD_HEIGHT - 1 - *fromY;
7971
7972     return whichMenu;
7973 }
7974
7975 void
7976 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7977 {
7978 //    char * hint = lastHint;
7979     FrontEndProgramStats stats;
7980
7981     stats.which = cps == &first ? 0 : 1;
7982     stats.depth = cpstats->depth;
7983     stats.nodes = cpstats->nodes;
7984     stats.score = cpstats->score;
7985     stats.time = cpstats->time;
7986     stats.pv = cpstats->movelist;
7987     stats.hint = lastHint;
7988     stats.an_move_index = 0;
7989     stats.an_move_count = 0;
7990
7991     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7992         stats.hint = cpstats->move_name;
7993         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7994         stats.an_move_count = cpstats->nr_moves;
7995     }
7996
7997     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
7998
7999     SetProgramStats( &stats );
8000 }
8001
8002 void
8003 ClearEngineOutputPane (int which)
8004 {
8005     static FrontEndProgramStats dummyStats;
8006     dummyStats.which = which;
8007     dummyStats.pv = "#";
8008     SetProgramStats( &dummyStats );
8009 }
8010
8011 #define MAXPLAYERS 500
8012
8013 char *
8014 TourneyStandings (int display)
8015 {
8016     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8017     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8018     char result, *p, *names[MAXPLAYERS];
8019
8020     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8021         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8022     names[0] = p = strdup(appData.participants);
8023     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8024
8025     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8026
8027     while(result = appData.results[nr]) {
8028         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8029         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8030         wScore = bScore = 0;
8031         switch(result) {
8032           case '+': wScore = 2; break;
8033           case '-': bScore = 2; break;
8034           case '=': wScore = bScore = 1; break;
8035           case ' ':
8036           case '*': return strdup("busy"); // tourney not finished
8037         }
8038         score[w] += wScore;
8039         score[b] += bScore;
8040         games[w]++;
8041         games[b]++;
8042         nr++;
8043     }
8044     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8045     for(w=0; w<nPlayers; w++) {
8046         bScore = -1;
8047         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8048         ranking[w] = b; points[w] = bScore; score[b] = -2;
8049     }
8050     p = malloc(nPlayers*34+1);
8051     for(w=0; w<nPlayers && w<display; w++)
8052         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8053     free(names[0]);
8054     return p;
8055 }
8056
8057 void
8058 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8059 {       // count all piece types
8060         int p, f, r;
8061         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8062         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8063         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8064                 p = board[r][f];
8065                 pCnt[p]++;
8066                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8067                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8068                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8069                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8070                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8071                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8072         }
8073 }
8074
8075 int
8076 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8077 {
8078         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8079         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8080
8081         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8082         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8083         if(myPawns == 2 && nMine == 3) // KPP
8084             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8085         if(myPawns == 1 && nMine == 2) // KP
8086             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8087         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8088             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8089         if(myPawns) return FALSE;
8090         if(pCnt[WhiteRook+side])
8091             return pCnt[BlackRook-side] ||
8092                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8093                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8094                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8095         if(pCnt[WhiteCannon+side]) {
8096             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8097             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8098         }
8099         if(pCnt[WhiteKnight+side])
8100             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8101         return FALSE;
8102 }
8103
8104 int
8105 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8106 {
8107         VariantClass v = gameInfo.variant;
8108
8109         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8110         if(v == VariantShatranj) return TRUE; // always winnable through baring
8111         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8112         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8113
8114         if(v == VariantXiangqi) {
8115                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8116
8117                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8118                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8119                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8120                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8121                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8122                 if(stale) // we have at least one last-rank P plus perhaps C
8123                     return majors // KPKX
8124                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8125                 else // KCA*E*
8126                     return pCnt[WhiteFerz+side] // KCAK
8127                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8128                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8129                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8130
8131         } else if(v == VariantKnightmate) {
8132                 if(nMine == 1) return FALSE;
8133                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8134         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8135                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8136
8137                 if(nMine == 1) return FALSE; // bare King
8138                 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
8139                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8140                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8141                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8142                 if(pCnt[WhiteKnight+side])
8143                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8144                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8145                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8146                 if(nBishops)
8147                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8148                 if(pCnt[WhiteAlfil+side])
8149                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8150                 if(pCnt[WhiteWazir+side])
8151                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8152         }
8153
8154         return TRUE;
8155 }
8156
8157 int
8158 CompareWithRights (Board b1, Board b2)
8159 {
8160     int rights = 0;
8161     if(!CompareBoards(b1, b2)) return FALSE;
8162     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8163     /* compare castling rights */
8164     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8165            rights++; /* King lost rights, while rook still had them */
8166     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8167         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8168            rights++; /* but at least one rook lost them */
8169     }
8170     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8171            rights++;
8172     if( b1[CASTLING][5] != NoRights ) {
8173         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8174            rights++;
8175     }
8176     return rights == 0;
8177 }
8178
8179 int
8180 Adjudicate (ChessProgramState *cps)
8181 {       // [HGM] some adjudications useful with buggy engines
8182         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8183         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8184         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8185         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8186         int k, drop, count = 0; static int bare = 1;
8187         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8188         Boolean canAdjudicate = !appData.icsActive;
8189
8190         // most tests only when we understand the game, i.e. legality-checking on
8191             if( appData.testLegality )
8192             {   /* [HGM] Some more adjudications for obstinate engines */
8193                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8194                 static int moveCount = 6;
8195                 ChessMove result;
8196                 char *reason = NULL;
8197
8198                 /* Count what is on board. */
8199                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8200
8201                 /* Some material-based adjudications that have to be made before stalemate test */
8202                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8203                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8204                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8205                      if(canAdjudicate && appData.checkMates) {
8206                          if(engineOpponent)
8207                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8208                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8209                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8210                          return 1;
8211                      }
8212                 }
8213
8214                 /* Bare King in Shatranj (loses) or Losers (wins) */
8215                 if( nrW == 1 || nrB == 1) {
8216                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8217                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8218                      if(canAdjudicate && appData.checkMates) {
8219                          if(engineOpponent)
8220                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8221                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8222                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8223                          return 1;
8224                      }
8225                   } else
8226                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8227                   {    /* bare King */
8228                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8229                         if(canAdjudicate && appData.checkMates) {
8230                             /* but only adjudicate if adjudication enabled */
8231                             if(engineOpponent)
8232                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8233                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8234                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8235                             return 1;
8236                         }
8237                   }
8238                 } else bare = 1;
8239
8240
8241             // don't wait for engine to announce game end if we can judge ourselves
8242             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8243               case MT_CHECK:
8244                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8245                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8246                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8247                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8248                             checkCnt++;
8249                         if(checkCnt >= 2) {
8250                             reason = "Xboard adjudication: 3rd check";
8251                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8252                             break;
8253                         }
8254                     }
8255                 }
8256               case MT_NONE:
8257               default:
8258                 break;
8259               case MT_STEALMATE:
8260               case MT_STALEMATE:
8261               case MT_STAINMATE:
8262                 reason = "Xboard adjudication: Stalemate";
8263                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8264                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8265                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8266                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8267                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8268                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8269                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8270                                                                         EP_CHECKMATE : EP_WINS);
8271                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8272                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8273                 }
8274                 break;
8275               case MT_CHECKMATE:
8276                 reason = "Xboard adjudication: Checkmate";
8277                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8278                 if(gameInfo.variant == VariantShogi) {
8279                     if(forwardMostMove > backwardMostMove
8280                        && moveList[forwardMostMove-1][1] == '@'
8281                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8282                         reason = "XBoard adjudication: pawn-drop mate";
8283                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8284                     }
8285                 }
8286                 break;
8287             }
8288
8289                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8290                     case EP_STALEMATE:
8291                         result = GameIsDrawn; break;
8292                     case EP_CHECKMATE:
8293                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8294                     case EP_WINS:
8295                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8296                     default:
8297                         result = EndOfFile;
8298                 }
8299                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8300                     if(engineOpponent)
8301                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8302                     GameEnds( result, reason, GE_XBOARD );
8303                     return 1;
8304                 }
8305
8306                 /* Next absolutely insufficient mating material. */
8307                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8308                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8309                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8310
8311                      /* always flag draws, for judging claims */
8312                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8313
8314                      if(canAdjudicate && appData.materialDraws) {
8315                          /* but only adjudicate them if adjudication enabled */
8316                          if(engineOpponent) {
8317                            SendToProgram("force\n", engineOpponent); // suppress reply
8318                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8319                          }
8320                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8321                          return 1;
8322                      }
8323                 }
8324
8325                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8326                 if(gameInfo.variant == VariantXiangqi ?
8327                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8328                  : nrW + nrB == 4 &&
8329                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8330                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8331                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8332                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8333                    ) ) {
8334                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8335                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8336                           if(engineOpponent) {
8337                             SendToProgram("force\n", engineOpponent); // suppress reply
8338                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8339                           }
8340                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8341                           return 1;
8342                      }
8343                 } else moveCount = 6;
8344             }
8345
8346         // Repetition draws and 50-move rule can be applied independently of legality testing
8347
8348                 /* Check for rep-draws */
8349                 count = 0;
8350                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8351                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8352                 for(k = forwardMostMove-2;
8353                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8354                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8355                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8356                     k-=2)
8357                 {   int rights=0;
8358                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8359                         /* compare castling rights */
8360                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8361                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8362                                 rights++; /* King lost rights, while rook still had them */
8363                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8364                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8365                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8366                                    rights++; /* but at least one rook lost them */
8367                         }
8368                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8369                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8370                                 rights++;
8371                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8372                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8373                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8374                                    rights++;
8375                         }
8376                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8377                             && appData.drawRepeats > 1) {
8378                              /* adjudicate after user-specified nr of repeats */
8379                              int result = GameIsDrawn;
8380                              char *details = "XBoard adjudication: repetition draw";
8381                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8382                                 // [HGM] xiangqi: check for forbidden perpetuals
8383                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8384                                 for(m=forwardMostMove; m>k; m-=2) {
8385                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8386                                         ourPerpetual = 0; // the current mover did not always check
8387                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8388                                         hisPerpetual = 0; // the opponent did not always check
8389                                 }
8390                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8391                                                                         ourPerpetual, hisPerpetual);
8392                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8393                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8394                                     details = "Xboard adjudication: perpetual checking";
8395                                 } else
8396                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8397                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8398                                 } else
8399                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8400                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8401                                         result = BlackWins;
8402                                         details = "Xboard adjudication: repetition";
8403                                     }
8404                                 } else // it must be XQ
8405                                 // Now check for perpetual chases
8406                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8407                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8408                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8409                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8410                                         static char resdet[MSG_SIZ];
8411                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8412                                         details = resdet;
8413                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8414                                     } else
8415                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8416                                         break; // Abort repetition-checking loop.
8417                                 }
8418                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8419                              }
8420                              if(engineOpponent) {
8421                                SendToProgram("force\n", engineOpponent); // suppress reply
8422                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8423                              }
8424                              GameEnds( result, details, GE_XBOARD );
8425                              return 1;
8426                         }
8427                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8428                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8429                     }
8430                 }
8431
8432                 /* Now we test for 50-move draws. Determine ply count */
8433                 count = forwardMostMove;
8434                 /* look for last irreversble move */
8435                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8436                     count--;
8437                 /* if we hit starting position, add initial plies */
8438                 if( count == backwardMostMove )
8439                     count -= initialRulePlies;
8440                 count = forwardMostMove - count;
8441                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8442                         // adjust reversible move counter for checks in Xiangqi
8443                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8444                         if(i < backwardMostMove) i = backwardMostMove;
8445                         while(i <= forwardMostMove) {
8446                                 lastCheck = inCheck; // check evasion does not count
8447                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8448                                 if(inCheck || lastCheck) count--; // check does not count
8449                                 i++;
8450                         }
8451                 }
8452                 if( count >= 100)
8453                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8454                          /* this is used to judge if draw claims are legal */
8455                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8456                          if(engineOpponent) {
8457                            SendToProgram("force\n", engineOpponent); // suppress reply
8458                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8459                          }
8460                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8461                          return 1;
8462                 }
8463
8464                 /* if draw offer is pending, treat it as a draw claim
8465                  * when draw condition present, to allow engines a way to
8466                  * claim draws before making their move to avoid a race
8467                  * condition occurring after their move
8468                  */
8469                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8470                          char *p = NULL;
8471                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8472                              p = "Draw claim: 50-move rule";
8473                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8474                              p = "Draw claim: 3-fold repetition";
8475                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8476                              p = "Draw claim: insufficient mating material";
8477                          if( p != NULL && canAdjudicate) {
8478                              if(engineOpponent) {
8479                                SendToProgram("force\n", engineOpponent); // suppress reply
8480                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8481                              }
8482                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8483                              return 1;
8484                          }
8485                 }
8486
8487                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8488                     if(engineOpponent) {
8489                       SendToProgram("force\n", engineOpponent); // suppress reply
8490                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8491                     }
8492                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8493                     return 1;
8494                 }
8495         return 0;
8496 }
8497
8498 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8499 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8500 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8501
8502 static int
8503 BitbaseProbe ()
8504 {
8505     int pieces[10], squares[10], cnt=0, r, f, res;
8506     static int loaded;
8507     static PPROBE_EGBB probeBB;
8508     if(!appData.testLegality) return 10;
8509     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8510     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8511     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8512     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8513         ChessSquare piece = boards[forwardMostMove][r][f];
8514         int black = (piece >= BlackPawn);
8515         int type = piece - black*BlackPawn;
8516         if(piece == EmptySquare) continue;
8517         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8518         if(type == WhiteKing) type = WhiteQueen + 1;
8519         type = egbbCode[type];
8520         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8521         pieces[cnt] = type + black*6;
8522         if(++cnt > 5) return 11;
8523     }
8524     pieces[cnt] = squares[cnt] = 0;
8525     // probe EGBB
8526     if(loaded == 2) return 13; // loading failed before
8527     if(loaded == 0) {
8528         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8529         HMODULE lib;
8530         PLOAD_EGBB loadBB;
8531         loaded = 2; // prepare for failure
8532         if(!path) return 13; // no egbb installed
8533         strncpy(buf, path + 8, MSG_SIZ);
8534         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8535         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8536         lib = LoadLibrary(buf);
8537         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8538         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8539         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8540         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8541         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8542         loaded = 1; // success!
8543     }
8544     res = probeBB(forwardMostMove & 1, pieces, squares);
8545     return res > 0 ? 1 : res < 0 ? -1 : 0;
8546 }
8547
8548 char *
8549 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8550 {   // [HGM] book: this routine intercepts moves to simulate book replies
8551     char *bookHit = NULL;
8552
8553     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8554         char buf[MSG_SIZ];
8555         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8556         SendToProgram(buf, cps);
8557     }
8558     //first determine if the incoming move brings opponent into his book
8559     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8560         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8561     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8562     if(bookHit != NULL && !cps->bookSuspend) {
8563         // make sure opponent is not going to reply after receiving move to book position
8564         SendToProgram("force\n", cps);
8565         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8566     }
8567     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8568     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8569     // now arrange restart after book miss
8570     if(bookHit) {
8571         // after a book hit we never send 'go', and the code after the call to this routine
8572         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8573         char buf[MSG_SIZ], *move = bookHit;
8574         if(cps->useSAN) {
8575             int fromX, fromY, toX, toY;
8576             char promoChar;
8577             ChessMove moveType;
8578             move = buf + 30;
8579             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8580                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8581                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8582                                     PosFlags(forwardMostMove),
8583                                     fromY, fromX, toY, toX, promoChar, move);
8584             } else {
8585                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8586                 bookHit = NULL;
8587             }
8588         }
8589         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8590         SendToProgram(buf, cps);
8591         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8592     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8593         SendToProgram("go\n", cps);
8594         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8595     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8596         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8597             SendToProgram("go\n", cps);
8598         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8599     }
8600     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8601 }
8602
8603 int
8604 LoadError (char *errmess, ChessProgramState *cps)
8605 {   // unloads engine and switches back to -ncp mode if it was first
8606     if(cps->initDone) return FALSE;
8607     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8608     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8609     cps->pr = NoProc;
8610     if(cps == &first) {
8611         appData.noChessProgram = TRUE;
8612         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8613         gameMode = BeginningOfGame; ModeHighlight();
8614         SetNCPMode();
8615     }
8616     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8617     DisplayMessage("", ""); // erase waiting message
8618     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8619     return TRUE;
8620 }
8621
8622 char *savedMessage;
8623 ChessProgramState *savedState;
8624 void
8625 DeferredBookMove (void)
8626 {
8627         if(savedState->lastPing != savedState->lastPong)
8628                     ScheduleDelayedEvent(DeferredBookMove, 10);
8629         else
8630         HandleMachineMove(savedMessage, savedState);
8631 }
8632
8633 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8634 static ChessProgramState *stalledEngine;
8635 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8636
8637 void
8638 HandleMachineMove (char *message, ChessProgramState *cps)
8639 {
8640     static char firstLeg[20];
8641     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8642     char realname[MSG_SIZ];
8643     int fromX, fromY, toX, toY;
8644     ChessMove moveType;
8645     char promoChar, roar;
8646     char *p, *pv=buf1;
8647     int oldError;
8648     char *bookHit;
8649
8650     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8651         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8652         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8653             DisplayError(_("Invalid pairing from pairing engine"), 0);
8654             return;
8655         }
8656         pairingReceived = 1;
8657         NextMatchGame();
8658         return; // Skim the pairing messages here.
8659     }
8660
8661     oldError = cps->userError; cps->userError = 0;
8662
8663 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8664     /*
8665      * Kludge to ignore BEL characters
8666      */
8667     while (*message == '\007') message++;
8668
8669     /*
8670      * [HGM] engine debug message: ignore lines starting with '#' character
8671      */
8672     if(cps->debug && *message == '#') return;
8673
8674     /*
8675      * Look for book output
8676      */
8677     if (cps == &first && bookRequested) {
8678         if (message[0] == '\t' || message[0] == ' ') {
8679             /* Part of the book output is here; append it */
8680             strcat(bookOutput, message);
8681             strcat(bookOutput, "  \n");
8682             return;
8683         } else if (bookOutput[0] != NULLCHAR) {
8684             /* All of book output has arrived; display it */
8685             char *p = bookOutput;
8686             while (*p != NULLCHAR) {
8687                 if (*p == '\t') *p = ' ';
8688                 p++;
8689             }
8690             DisplayInformation(bookOutput);
8691             bookRequested = FALSE;
8692             /* Fall through to parse the current output */
8693         }
8694     }
8695
8696     /*
8697      * Look for machine move.
8698      */
8699     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8700         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8701     {
8702         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8703             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8704             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8705             stalledEngine = cps;
8706             if(appData.ponderNextMove) { // bring opponent out of ponder
8707                 if(gameMode == TwoMachinesPlay) {
8708                     if(cps->other->pause)
8709                         PauseEngine(cps->other);
8710                     else
8711                         SendToProgram("easy\n", cps->other);
8712                 }
8713             }
8714             StopClocks();
8715             return;
8716         }
8717
8718       if(cps->usePing) {
8719
8720         /* This method is only useful on engines that support ping */
8721         if(abortEngineThink) {
8722             if (appData.debugMode) {
8723                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8724             }
8725             SendToProgram("undo\n", cps);
8726             return;
8727         }
8728
8729         if (cps->lastPing != cps->lastPong) {
8730             /* Extra move from before last new; ignore */
8731             if (appData.debugMode) {
8732                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8733             }
8734           return;
8735         }
8736
8737       } else {
8738
8739         int machineWhite = FALSE;
8740
8741         switch (gameMode) {
8742           case BeginningOfGame:
8743             /* Extra move from before last reset; ignore */
8744             if (appData.debugMode) {
8745                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8746             }
8747             return;
8748
8749           case EndOfGame:
8750           case IcsIdle:
8751           default:
8752             /* Extra move after we tried to stop.  The mode test is
8753                not a reliable way of detecting this problem, but it's
8754                the best we can do on engines that don't support ping.
8755             */
8756             if (appData.debugMode) {
8757                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8758                         cps->which, gameMode);
8759             }
8760             SendToProgram("undo\n", cps);
8761             return;
8762
8763           case MachinePlaysWhite:
8764           case IcsPlayingWhite:
8765             machineWhite = TRUE;
8766             break;
8767
8768           case MachinePlaysBlack:
8769           case IcsPlayingBlack:
8770             machineWhite = FALSE;
8771             break;
8772
8773           case TwoMachinesPlay:
8774             machineWhite = (cps->twoMachinesColor[0] == 'w');
8775             break;
8776         }
8777         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8778             if (appData.debugMode) {
8779                 fprintf(debugFP,
8780                         "Ignoring move out of turn by %s, gameMode %d"
8781                         ", forwardMost %d\n",
8782                         cps->which, gameMode, forwardMostMove);
8783             }
8784             return;
8785         }
8786       }
8787
8788         if(cps->alphaRank) AlphaRank(machineMove, 4);
8789
8790         // [HGM] lion: (some very limited) support for Alien protocol
8791         killX = killY = kill2X = kill2Y = -1;
8792         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8793             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8794             return;
8795         }
8796         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8797             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8798             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8799         }
8800         if(firstLeg[0]) { // there was a previous leg;
8801             // only support case where same piece makes two step
8802             char buf[20], *p = machineMove+1, *q = buf+1, f;
8803             safeStrCpy(buf, machineMove, 20);
8804             while(isdigit(*q)) q++; // find start of to-square
8805             safeStrCpy(machineMove, firstLeg, 20);
8806             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8807             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8808             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8809             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8810             firstLeg[0] = NULLCHAR;
8811         }
8812
8813         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8814                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8815             /* Machine move could not be parsed; ignore it. */
8816           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8817                     machineMove, _(cps->which));
8818             DisplayMoveError(buf1);
8819             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8820                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8821             if (gameMode == TwoMachinesPlay) {
8822               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8823                        buf1, GE_XBOARD);
8824             }
8825             return;
8826         }
8827
8828         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8829         /* So we have to redo legality test with true e.p. status here,  */
8830         /* to make sure an illegal e.p. capture does not slip through,   */
8831         /* to cause a forfeit on a justified illegal-move complaint      */
8832         /* of the opponent.                                              */
8833         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8834            ChessMove moveType;
8835            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8836                              fromY, fromX, toY, toX, promoChar);
8837             if(moveType == IllegalMove) {
8838               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8839                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8840                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8841                            buf1, GE_XBOARD);
8842                 return;
8843            } else if(!appData.fischerCastling)
8844            /* [HGM] Kludge to handle engines that send FRC-style castling
8845               when they shouldn't (like TSCP-Gothic) */
8846            switch(moveType) {
8847              case WhiteASideCastleFR:
8848              case BlackASideCastleFR:
8849                toX+=2;
8850                currentMoveString[2]++;
8851                break;
8852              case WhiteHSideCastleFR:
8853              case BlackHSideCastleFR:
8854                toX--;
8855                currentMoveString[2]--;
8856                break;
8857              default: ; // nothing to do, but suppresses warning of pedantic compilers
8858            }
8859         }
8860         hintRequested = FALSE;
8861         lastHint[0] = NULLCHAR;
8862         bookRequested = FALSE;
8863         /* Program may be pondering now */
8864         cps->maybeThinking = TRUE;
8865         if (cps->sendTime == 2) cps->sendTime = 1;
8866         if (cps->offeredDraw) cps->offeredDraw--;
8867
8868         /* [AS] Save move info*/
8869         pvInfoList[ forwardMostMove ].score = programStats.score;
8870         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8871         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8872
8873         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8874
8875         /* Test suites abort the 'game' after one move */
8876         if(*appData.finger) {
8877            static FILE *f;
8878            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8879            if(!f) f = fopen(appData.finger, "w");
8880            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8881            else { DisplayFatalError("Bad output file", errno, 0); return; }
8882            free(fen);
8883            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8884         }
8885         if(appData.epd) {
8886            if(solvingTime >= 0) {
8887               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8888               totalTime += solvingTime; first.matchWins++;
8889            } else {
8890               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8891               second.matchWins++;
8892            }
8893            OutputKibitz(2, buf1);
8894            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8895         }
8896
8897         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8898         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8899             int count = 0;
8900
8901             while( count < adjudicateLossPlies ) {
8902                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8903
8904                 if( count & 1 ) {
8905                     score = -score; /* Flip score for winning side */
8906                 }
8907
8908                 if( score > appData.adjudicateLossThreshold ) {
8909                     break;
8910                 }
8911
8912                 count++;
8913             }
8914
8915             if( count >= adjudicateLossPlies ) {
8916                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8917
8918                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8919                     "Xboard adjudication",
8920                     GE_XBOARD );
8921
8922                 return;
8923             }
8924         }
8925
8926         if(Adjudicate(cps)) {
8927             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8928             return; // [HGM] adjudicate: for all automatic game ends
8929         }
8930
8931 #if ZIPPY
8932         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8933             first.initDone) {
8934           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8935                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8936                 SendToICS("draw ");
8937                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8938           }
8939           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8940           ics_user_moved = 1;
8941           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8942                 char buf[3*MSG_SIZ];
8943
8944                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8945                         programStats.score / 100.,
8946                         programStats.depth,
8947                         programStats.time / 100.,
8948                         (unsigned int)programStats.nodes,
8949                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8950                         programStats.movelist);
8951                 SendToICS(buf);
8952           }
8953         }
8954 #endif
8955
8956         /* [AS] Clear stats for next move */
8957         ClearProgramStats();
8958         thinkOutput[0] = NULLCHAR;
8959         hiddenThinkOutputState = 0;
8960
8961         bookHit = NULL;
8962         if (gameMode == TwoMachinesPlay) {
8963             /* [HGM] relaying draw offers moved to after reception of move */
8964             /* and interpreting offer as claim if it brings draw condition */
8965             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8966                 SendToProgram("draw\n", cps->other);
8967             }
8968             if (cps->other->sendTime) {
8969                 SendTimeRemaining(cps->other,
8970                                   cps->other->twoMachinesColor[0] == 'w');
8971             }
8972             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8973             if (firstMove && !bookHit) {
8974                 firstMove = FALSE;
8975                 if (cps->other->useColors) {
8976                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8977                 }
8978                 SendToProgram("go\n", cps->other);
8979             }
8980             cps->other->maybeThinking = TRUE;
8981         }
8982
8983         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8984
8985         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8986
8987         if (!pausing && appData.ringBellAfterMoves) {
8988             if(!roar) RingBell();
8989         }
8990
8991         /*
8992          * Reenable menu items that were disabled while
8993          * machine was thinking
8994          */
8995         if (gameMode != TwoMachinesPlay)
8996             SetUserThinkingEnables();
8997
8998         // [HGM] book: after book hit opponent has received move and is now in force mode
8999         // force the book reply into it, and then fake that it outputted this move by jumping
9000         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9001         if(bookHit) {
9002                 static char bookMove[MSG_SIZ]; // a bit generous?
9003
9004                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9005                 strcat(bookMove, bookHit);
9006                 message = bookMove;
9007                 cps = cps->other;
9008                 programStats.nodes = programStats.depth = programStats.time =
9009                 programStats.score = programStats.got_only_move = 0;
9010                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9011
9012                 if(cps->lastPing != cps->lastPong) {
9013                     savedMessage = message; // args for deferred call
9014                     savedState = cps;
9015                     ScheduleDelayedEvent(DeferredBookMove, 10);
9016                     return;
9017                 }
9018                 goto FakeBookMove;
9019         }
9020
9021         return;
9022     }
9023
9024     /* Set special modes for chess engines.  Later something general
9025      *  could be added here; for now there is just one kludge feature,
9026      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9027      *  when "xboard" is given as an interactive command.
9028      */
9029     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9030         cps->useSigint = FALSE;
9031         cps->useSigterm = FALSE;
9032     }
9033     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9034       ParseFeatures(message+8, cps);
9035       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9036     }
9037
9038     if (!strncmp(message, "setup ", 6) && 
9039         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9040           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9041                                         ) { // [HGM] allow first engine to define opening position
9042       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9043       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9044       *buf = NULLCHAR;
9045       if(sscanf(message, "setup (%s", buf) == 1) {
9046         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9047         ASSIGN(appData.pieceToCharTable, buf);
9048       }
9049       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9050       if(dummy >= 3) {
9051         while(message[s] && message[s++] != ' ');
9052         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9053            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9054             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9055             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9056           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9057           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9058           startedFromSetupPosition = FALSE;
9059         }
9060       }
9061       if(startedFromSetupPosition) return;
9062       ParseFEN(boards[0], &dummy, message+s, FALSE);
9063       DrawPosition(TRUE, boards[0]);
9064       CopyBoard(initialPosition, boards[0]);
9065       startedFromSetupPosition = TRUE;
9066       return;
9067     }
9068     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9069       ChessSquare piece = WhitePawn;
9070       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9071       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9072       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9073       piece += CharToPiece(ID & 255) - WhitePawn;
9074       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9075       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9076       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9077       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9078       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9079       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9080                                                && gameInfo.variant != VariantGreat
9081                                                && gameInfo.variant != VariantFairy    ) return;
9082       if(piece < EmptySquare) {
9083         pieceDefs = TRUE;
9084         ASSIGN(pieceDesc[piece], buf1);
9085         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9086       }
9087       return;
9088     }
9089     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9090       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9091       Sweep(0);
9092       return;
9093     }
9094     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9095      * want this, I was asked to put it in, and obliged.
9096      */
9097     if (!strncmp(message, "setboard ", 9)) {
9098         Board initial_position;
9099
9100         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9101
9102         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9103             DisplayError(_("Bad FEN received from engine"), 0);
9104             return ;
9105         } else {
9106            Reset(TRUE, FALSE);
9107            CopyBoard(boards[0], initial_position);
9108            initialRulePlies = FENrulePlies;
9109            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9110            else gameMode = MachinePlaysBlack;
9111            DrawPosition(FALSE, boards[currentMove]);
9112         }
9113         return;
9114     }
9115
9116     /*
9117      * Look for communication commands
9118      */
9119     if (!strncmp(message, "telluser ", 9)) {
9120         if(message[9] == '\\' && message[10] == '\\')
9121             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9122         PlayTellSound();
9123         DisplayNote(message + 9);
9124         return;
9125     }
9126     if (!strncmp(message, "tellusererror ", 14)) {
9127         cps->userError = 1;
9128         if(message[14] == '\\' && message[15] == '\\')
9129             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9130         PlayTellSound();
9131         DisplayError(message + 14, 0);
9132         return;
9133     }
9134     if (!strncmp(message, "tellopponent ", 13)) {
9135       if (appData.icsActive) {
9136         if (loggedOn) {
9137           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9138           SendToICS(buf1);
9139         }
9140       } else {
9141         DisplayNote(message + 13);
9142       }
9143       return;
9144     }
9145     if (!strncmp(message, "tellothers ", 11)) {
9146       if (appData.icsActive) {
9147         if (loggedOn) {
9148           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9149           SendToICS(buf1);
9150         }
9151       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9152       return;
9153     }
9154     if (!strncmp(message, "tellall ", 8)) {
9155       if (appData.icsActive) {
9156         if (loggedOn) {
9157           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9158           SendToICS(buf1);
9159         }
9160       } else {
9161         DisplayNote(message + 8);
9162       }
9163       return;
9164     }
9165     if (strncmp(message, "warning", 7) == 0) {
9166         /* Undocumented feature, use tellusererror in new code */
9167         DisplayError(message, 0);
9168         return;
9169     }
9170     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9171         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9172         strcat(realname, " query");
9173         AskQuestion(realname, buf2, buf1, cps->pr);
9174         return;
9175     }
9176     /* Commands from the engine directly to ICS.  We don't allow these to be
9177      *  sent until we are logged on. Crafty kibitzes have been known to
9178      *  interfere with the login process.
9179      */
9180     if (loggedOn) {
9181         if (!strncmp(message, "tellics ", 8)) {
9182             SendToICS(message + 8);
9183             SendToICS("\n");
9184             return;
9185         }
9186         if (!strncmp(message, "tellicsnoalias ", 15)) {
9187             SendToICS(ics_prefix);
9188             SendToICS(message + 15);
9189             SendToICS("\n");
9190             return;
9191         }
9192         /* The following are for backward compatibility only */
9193         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9194             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9195             SendToICS(ics_prefix);
9196             SendToICS(message);
9197             SendToICS("\n");
9198             return;
9199         }
9200     }
9201     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9202         if(initPing == cps->lastPong) {
9203             if(gameInfo.variant == VariantUnknown) {
9204                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9205                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9206                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9207             }
9208             initPing = -1;
9209         }
9210         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9211             abortEngineThink = FALSE;
9212             DisplayMessage("", "");
9213             ThawUI();
9214         }
9215         return;
9216     }
9217     if(!strncmp(message, "highlight ", 10)) {
9218         if(appData.testLegality && !*engineVariant && appData.markers) return;
9219         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9220         return;
9221     }
9222     if(!strncmp(message, "click ", 6)) {
9223         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9224         if(appData.testLegality || !appData.oneClick) return;
9225         sscanf(message+6, "%c%d%c", &f, &y, &c);
9226         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9227         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9228         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9229         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9230         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9231         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9232             LeftClick(Release, lastLeftX, lastLeftY);
9233         controlKey  = (c == ',');
9234         LeftClick(Press, x, y);
9235         LeftClick(Release, x, y);
9236         first.highlight = f;
9237         return;
9238     }
9239     /*
9240      * If the move is illegal, cancel it and redraw the board.
9241      * Also deal with other error cases.  Matching is rather loose
9242      * here to accommodate engines written before the spec.
9243      */
9244     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9245         strncmp(message, "Error", 5) == 0) {
9246         if (StrStr(message, "name") ||
9247             StrStr(message, "rating") || StrStr(message, "?") ||
9248             StrStr(message, "result") || StrStr(message, "board") ||
9249             StrStr(message, "bk") || StrStr(message, "computer") ||
9250             StrStr(message, "variant") || StrStr(message, "hint") ||
9251             StrStr(message, "random") || StrStr(message, "depth") ||
9252             StrStr(message, "accepted")) {
9253             return;
9254         }
9255         if (StrStr(message, "protover")) {
9256           /* Program is responding to input, so it's apparently done
9257              initializing, and this error message indicates it is
9258              protocol version 1.  So we don't need to wait any longer
9259              for it to initialize and send feature commands. */
9260           FeatureDone(cps, 1);
9261           cps->protocolVersion = 1;
9262           return;
9263         }
9264         cps->maybeThinking = FALSE;
9265
9266         if (StrStr(message, "draw")) {
9267             /* Program doesn't have "draw" command */
9268             cps->sendDrawOffers = 0;
9269             return;
9270         }
9271         if (cps->sendTime != 1 &&
9272             (StrStr(message, "time") || StrStr(message, "otim"))) {
9273           /* Program apparently doesn't have "time" or "otim" command */
9274           cps->sendTime = 0;
9275           return;
9276         }
9277         if (StrStr(message, "analyze")) {
9278             cps->analysisSupport = FALSE;
9279             cps->analyzing = FALSE;
9280 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9281             EditGameEvent(); // [HGM] try to preserve loaded game
9282             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9283             DisplayError(buf2, 0);
9284             return;
9285         }
9286         if (StrStr(message, "(no matching move)st")) {
9287           /* Special kludge for GNU Chess 4 only */
9288           cps->stKludge = TRUE;
9289           SendTimeControl(cps, movesPerSession, timeControl,
9290                           timeIncrement, appData.searchDepth,
9291                           searchTime);
9292           return;
9293         }
9294         if (StrStr(message, "(no matching move)sd")) {
9295           /* Special kludge for GNU Chess 4 only */
9296           cps->sdKludge = TRUE;
9297           SendTimeControl(cps, movesPerSession, timeControl,
9298                           timeIncrement, appData.searchDepth,
9299                           searchTime);
9300           return;
9301         }
9302         if (!StrStr(message, "llegal")) {
9303             return;
9304         }
9305         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9306             gameMode == IcsIdle) return;
9307         if (forwardMostMove <= backwardMostMove) return;
9308         if (pausing) PauseEvent();
9309       if(appData.forceIllegal) {
9310             // [HGM] illegal: machine refused move; force position after move into it
9311           SendToProgram("force\n", cps);
9312           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9313                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9314                 // when black is to move, while there might be nothing on a2 or black
9315                 // might already have the move. So send the board as if white has the move.
9316                 // But first we must change the stm of the engine, as it refused the last move
9317                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9318                 if(WhiteOnMove(forwardMostMove)) {
9319                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9320                     SendBoard(cps, forwardMostMove); // kludgeless board
9321                 } else {
9322                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9323                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9324                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9325                 }
9326           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9327             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9328                  gameMode == TwoMachinesPlay)
9329               SendToProgram("go\n", cps);
9330             return;
9331       } else
9332         if (gameMode == PlayFromGameFile) {
9333             /* Stop reading this game file */
9334             gameMode = EditGame;
9335             ModeHighlight();
9336         }
9337         /* [HGM] illegal-move claim should forfeit game when Xboard */
9338         /* only passes fully legal moves                            */
9339         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9340             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9341                                 "False illegal-move claim", GE_XBOARD );
9342             return; // do not take back move we tested as valid
9343         }
9344         currentMove = forwardMostMove-1;
9345         DisplayMove(currentMove-1); /* before DisplayMoveError */
9346         SwitchClocks(forwardMostMove-1); // [HGM] race
9347         DisplayBothClocks();
9348         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9349                 parseList[currentMove], _(cps->which));
9350         DisplayMoveError(buf1);
9351         DrawPosition(FALSE, boards[currentMove]);
9352
9353         SetUserThinkingEnables();
9354         return;
9355     }
9356     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9357         /* Program has a broken "time" command that
9358            outputs a string not ending in newline.
9359            Don't use it. */
9360         cps->sendTime = 0;
9361     }
9362     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9363         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9364             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9365     }
9366
9367     /*
9368      * If chess program startup fails, exit with an error message.
9369      * Attempts to recover here are futile. [HGM] Well, we try anyway
9370      */
9371     if ((StrStr(message, "unknown host") != NULL)
9372         || (StrStr(message, "No remote directory") != NULL)
9373         || (StrStr(message, "not found") != NULL)
9374         || (StrStr(message, "No such file") != NULL)
9375         || (StrStr(message, "can't alloc") != NULL)
9376         || (StrStr(message, "Permission denied") != NULL)) {
9377
9378         cps->maybeThinking = FALSE;
9379         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9380                 _(cps->which), cps->program, cps->host, message);
9381         RemoveInputSource(cps->isr);
9382         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9383             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9384             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9385         }
9386         return;
9387     }
9388
9389     /*
9390      * Look for hint output
9391      */
9392     if (sscanf(message, "Hint: %s", buf1) == 1) {
9393         if (cps == &first && hintRequested) {
9394             hintRequested = FALSE;
9395             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9396                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9397                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9398                                     PosFlags(forwardMostMove),
9399                                     fromY, fromX, toY, toX, promoChar, buf1);
9400                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9401                 DisplayInformation(buf2);
9402             } else {
9403                 /* Hint move could not be parsed!? */
9404               snprintf(buf2, sizeof(buf2),
9405                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9406                         buf1, _(cps->which));
9407                 DisplayError(buf2, 0);
9408             }
9409         } else {
9410           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9411         }
9412         return;
9413     }
9414
9415     /*
9416      * Ignore other messages if game is not in progress
9417      */
9418     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9419         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9420
9421     /*
9422      * look for win, lose, draw, or draw offer
9423      */
9424     if (strncmp(message, "1-0", 3) == 0) {
9425         char *p, *q, *r = "";
9426         p = strchr(message, '{');
9427         if (p) {
9428             q = strchr(p, '}');
9429             if (q) {
9430                 *q = NULLCHAR;
9431                 r = p + 1;
9432             }
9433         }
9434         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9435         return;
9436     } else if (strncmp(message, "0-1", 3) == 0) {
9437         char *p, *q, *r = "";
9438         p = strchr(message, '{');
9439         if (p) {
9440             q = strchr(p, '}');
9441             if (q) {
9442                 *q = NULLCHAR;
9443                 r = p + 1;
9444             }
9445         }
9446         /* Kludge for Arasan 4.1 bug */
9447         if (strcmp(r, "Black resigns") == 0) {
9448             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9449             return;
9450         }
9451         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9452         return;
9453     } else if (strncmp(message, "1/2", 3) == 0) {
9454         char *p, *q, *r = "";
9455         p = strchr(message, '{');
9456         if (p) {
9457             q = strchr(p, '}');
9458             if (q) {
9459                 *q = NULLCHAR;
9460                 r = p + 1;
9461             }
9462         }
9463
9464         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9465         return;
9466
9467     } else if (strncmp(message, "White resign", 12) == 0) {
9468         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9469         return;
9470     } else if (strncmp(message, "Black resign", 12) == 0) {
9471         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9472         return;
9473     } else if (strncmp(message, "White matches", 13) == 0 ||
9474                strncmp(message, "Black matches", 13) == 0   ) {
9475         /* [HGM] ignore GNUShogi noises */
9476         return;
9477     } else if (strncmp(message, "White", 5) == 0 &&
9478                message[5] != '(' &&
9479                StrStr(message, "Black") == NULL) {
9480         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9481         return;
9482     } else if (strncmp(message, "Black", 5) == 0 &&
9483                message[5] != '(') {
9484         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9485         return;
9486     } else if (strcmp(message, "resign") == 0 ||
9487                strcmp(message, "computer resigns") == 0) {
9488         switch (gameMode) {
9489           case MachinePlaysBlack:
9490           case IcsPlayingBlack:
9491             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9492             break;
9493           case MachinePlaysWhite:
9494           case IcsPlayingWhite:
9495             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9496             break;
9497           case TwoMachinesPlay:
9498             if (cps->twoMachinesColor[0] == 'w')
9499               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9500             else
9501               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9502             break;
9503           default:
9504             /* can't happen */
9505             break;
9506         }
9507         return;
9508     } else if (strncmp(message, "opponent mates", 14) == 0) {
9509         switch (gameMode) {
9510           case MachinePlaysBlack:
9511           case IcsPlayingBlack:
9512             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9513             break;
9514           case MachinePlaysWhite:
9515           case IcsPlayingWhite:
9516             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9517             break;
9518           case TwoMachinesPlay:
9519             if (cps->twoMachinesColor[0] == 'w')
9520               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9521             else
9522               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9523             break;
9524           default:
9525             /* can't happen */
9526             break;
9527         }
9528         return;
9529     } else if (strncmp(message, "computer mates", 14) == 0) {
9530         switch (gameMode) {
9531           case MachinePlaysBlack:
9532           case IcsPlayingBlack:
9533             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9534             break;
9535           case MachinePlaysWhite:
9536           case IcsPlayingWhite:
9537             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9538             break;
9539           case TwoMachinesPlay:
9540             if (cps->twoMachinesColor[0] == 'w')
9541               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9542             else
9543               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9544             break;
9545           default:
9546             /* can't happen */
9547             break;
9548         }
9549         return;
9550     } else if (strncmp(message, "checkmate", 9) == 0) {
9551         if (WhiteOnMove(forwardMostMove)) {
9552             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9553         } else {
9554             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9555         }
9556         return;
9557     } else if (strstr(message, "Draw") != NULL ||
9558                strstr(message, "game is a draw") != NULL) {
9559         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9560         return;
9561     } else if (strstr(message, "offer") != NULL &&
9562                strstr(message, "draw") != NULL) {
9563 #if ZIPPY
9564         if (appData.zippyPlay && first.initDone) {
9565             /* Relay offer to ICS */
9566             SendToICS(ics_prefix);
9567             SendToICS("draw\n");
9568         }
9569 #endif
9570         cps->offeredDraw = 2; /* valid until this engine moves twice */
9571         if (gameMode == TwoMachinesPlay) {
9572             if (cps->other->offeredDraw) {
9573                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9574             /* [HGM] in two-machine mode we delay relaying draw offer      */
9575             /* until after we also have move, to see if it is really claim */
9576             }
9577         } else if (gameMode == MachinePlaysWhite ||
9578                    gameMode == MachinePlaysBlack) {
9579           if (userOfferedDraw) {
9580             DisplayInformation(_("Machine accepts your draw offer"));
9581             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9582           } else {
9583             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9584           }
9585         }
9586     }
9587
9588
9589     /*
9590      * Look for thinking output
9591      */
9592     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9593           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9594                                 ) {
9595         int plylev, mvleft, mvtot, curscore, time;
9596         char mvname[MOVE_LEN];
9597         u64 nodes; // [DM]
9598         char plyext;
9599         int ignore = FALSE;
9600         int prefixHint = FALSE;
9601         mvname[0] = NULLCHAR;
9602
9603         switch (gameMode) {
9604           case MachinePlaysBlack:
9605           case IcsPlayingBlack:
9606             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9607             break;
9608           case MachinePlaysWhite:
9609           case IcsPlayingWhite:
9610             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9611             break;
9612           case AnalyzeMode:
9613           case AnalyzeFile:
9614             break;
9615           case IcsObserving: /* [DM] icsEngineAnalyze */
9616             if (!appData.icsEngineAnalyze) ignore = TRUE;
9617             break;
9618           case TwoMachinesPlay:
9619             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9620                 ignore = TRUE;
9621             }
9622             break;
9623           default:
9624             ignore = TRUE;
9625             break;
9626         }
9627
9628         if (!ignore) {
9629             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9630             buf1[0] = NULLCHAR;
9631             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9632                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9633                 char score_buf[MSG_SIZ];
9634
9635                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9636                     nodes += u64Const(0x100000000);
9637
9638                 if (plyext != ' ' && plyext != '\t') {
9639                     time *= 100;
9640                 }
9641
9642                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9643                 if( cps->scoreIsAbsolute &&
9644                     ( gameMode == MachinePlaysBlack ||
9645                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9646                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9647                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9648                      !WhiteOnMove(currentMove)
9649                     ) )
9650                 {
9651                     curscore = -curscore;
9652                 }
9653
9654                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9655
9656                 if(*bestMove) { // rememer time best EPD move was first found
9657                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9658                     ChessMove mt;
9659                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9660                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9661                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9662                 }
9663
9664                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9665                         char buf[MSG_SIZ];
9666                         FILE *f;
9667                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9668                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9669                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9670                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9671                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9672                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9673                                 fclose(f);
9674                         }
9675                         else
9676                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9677                           DisplayError(_("failed writing PV"), 0);
9678                 }
9679
9680                 tempStats.depth = plylev;
9681                 tempStats.nodes = nodes;
9682                 tempStats.time = time;
9683                 tempStats.score = curscore;
9684                 tempStats.got_only_move = 0;
9685
9686                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9687                         int ticklen;
9688
9689                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9690                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9691                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9692                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9693                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9694                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9695                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9696                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9697                 }
9698
9699                 /* Buffer overflow protection */
9700                 if (pv[0] != NULLCHAR) {
9701                     if (strlen(pv) >= sizeof(tempStats.movelist)
9702                         && appData.debugMode) {
9703                         fprintf(debugFP,
9704                                 "PV is too long; using the first %u bytes.\n",
9705                                 (unsigned) sizeof(tempStats.movelist) - 1);
9706                     }
9707
9708                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9709                 } else {
9710                     sprintf(tempStats.movelist, " no PV\n");
9711                 }
9712
9713                 if (tempStats.seen_stat) {
9714                     tempStats.ok_to_send = 1;
9715                 }
9716
9717                 if (strchr(tempStats.movelist, '(') != NULL) {
9718                     tempStats.line_is_book = 1;
9719                     tempStats.nr_moves = 0;
9720                     tempStats.moves_left = 0;
9721                 } else {
9722                     tempStats.line_is_book = 0;
9723                 }
9724
9725                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9726                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9727
9728                 SendProgramStatsToFrontend( cps, &tempStats );
9729
9730                 /*
9731                     [AS] Protect the thinkOutput buffer from overflow... this
9732                     is only useful if buf1 hasn't overflowed first!
9733                 */
9734                 if(curscore >= MATE_SCORE) 
9735                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9736                 else if(curscore <= -MATE_SCORE) 
9737                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9738                 else
9739                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9740                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9741                          plylev,
9742                          (gameMode == TwoMachinesPlay ?
9743                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9744                          score_buf,
9745                          prefixHint ? lastHint : "",
9746                          prefixHint ? " " : "" );
9747
9748                 if( buf1[0] != NULLCHAR ) {
9749                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9750
9751                     if( strlen(pv) > max_len ) {
9752                         if( appData.debugMode) {
9753                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9754                         }
9755                         pv[max_len+1] = '\0';
9756                     }
9757
9758                     strcat( thinkOutput, pv);
9759                 }
9760
9761                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9762                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9763                     DisplayMove(currentMove - 1);
9764                 }
9765                 return;
9766
9767             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9768                 /* crafty (9.25+) says "(only move) <move>"
9769                  * if there is only 1 legal move
9770                  */
9771                 sscanf(p, "(only move) %s", buf1);
9772                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9773                 sprintf(programStats.movelist, "%s (only move)", buf1);
9774                 programStats.depth = 1;
9775                 programStats.nr_moves = 1;
9776                 programStats.moves_left = 1;
9777                 programStats.nodes = 1;
9778                 programStats.time = 1;
9779                 programStats.got_only_move = 1;
9780
9781                 /* Not really, but we also use this member to
9782                    mean "line isn't going to change" (Crafty
9783                    isn't searching, so stats won't change) */
9784                 programStats.line_is_book = 1;
9785
9786                 SendProgramStatsToFrontend( cps, &programStats );
9787
9788                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9789                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9790                     DisplayMove(currentMove - 1);
9791                 }
9792                 return;
9793             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9794                               &time, &nodes, &plylev, &mvleft,
9795                               &mvtot, mvname) >= 5) {
9796                 /* The stat01: line is from Crafty (9.29+) in response
9797                    to the "." command */
9798                 programStats.seen_stat = 1;
9799                 cps->maybeThinking = TRUE;
9800
9801                 if (programStats.got_only_move || !appData.periodicUpdates)
9802                   return;
9803
9804                 programStats.depth = plylev;
9805                 programStats.time = time;
9806                 programStats.nodes = nodes;
9807                 programStats.moves_left = mvleft;
9808                 programStats.nr_moves = mvtot;
9809                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9810                 programStats.ok_to_send = 1;
9811                 programStats.movelist[0] = '\0';
9812
9813                 SendProgramStatsToFrontend( cps, &programStats );
9814
9815                 return;
9816
9817             } else if (strncmp(message,"++",2) == 0) {
9818                 /* Crafty 9.29+ outputs this */
9819                 programStats.got_fail = 2;
9820                 return;
9821
9822             } else if (strncmp(message,"--",2) == 0) {
9823                 /* Crafty 9.29+ outputs this */
9824                 programStats.got_fail = 1;
9825                 return;
9826
9827             } else if (thinkOutput[0] != NULLCHAR &&
9828                        strncmp(message, "    ", 4) == 0) {
9829                 unsigned message_len;
9830
9831                 p = message;
9832                 while (*p && *p == ' ') p++;
9833
9834                 message_len = strlen( p );
9835
9836                 /* [AS] Avoid buffer overflow */
9837                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9838                     strcat(thinkOutput, " ");
9839                     strcat(thinkOutput, p);
9840                 }
9841
9842                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9843                     strcat(programStats.movelist, " ");
9844                     strcat(programStats.movelist, p);
9845                 }
9846
9847                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9848                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9849                     DisplayMove(currentMove - 1);
9850                 }
9851                 return;
9852             }
9853         }
9854         else {
9855             buf1[0] = NULLCHAR;
9856
9857             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9858                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9859             {
9860                 ChessProgramStats cpstats;
9861
9862                 if (plyext != ' ' && plyext != '\t') {
9863                     time *= 100;
9864                 }
9865
9866                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9867                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9868                     curscore = -curscore;
9869                 }
9870
9871                 cpstats.depth = plylev;
9872                 cpstats.nodes = nodes;
9873                 cpstats.time = time;
9874                 cpstats.score = curscore;
9875                 cpstats.got_only_move = 0;
9876                 cpstats.movelist[0] = '\0';
9877
9878                 if (buf1[0] != NULLCHAR) {
9879                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9880                 }
9881
9882                 cpstats.ok_to_send = 0;
9883                 cpstats.line_is_book = 0;
9884                 cpstats.nr_moves = 0;
9885                 cpstats.moves_left = 0;
9886
9887                 SendProgramStatsToFrontend( cps, &cpstats );
9888             }
9889         }
9890     }
9891 }
9892
9893
9894 /* Parse a game score from the character string "game", and
9895    record it as the history of the current game.  The game
9896    score is NOT assumed to start from the standard position.
9897    The display is not updated in any way.
9898    */
9899 void
9900 ParseGameHistory (char *game)
9901 {
9902     ChessMove moveType;
9903     int fromX, fromY, toX, toY, boardIndex;
9904     char promoChar;
9905     char *p, *q;
9906     char buf[MSG_SIZ];
9907
9908     if (appData.debugMode)
9909       fprintf(debugFP, "Parsing game history: %s\n", game);
9910
9911     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9912     gameInfo.site = StrSave(appData.icsHost);
9913     gameInfo.date = PGNDate();
9914     gameInfo.round = StrSave("-");
9915
9916     /* Parse out names of players */
9917     while (*game == ' ') game++;
9918     p = buf;
9919     while (*game != ' ') *p++ = *game++;
9920     *p = NULLCHAR;
9921     gameInfo.white = StrSave(buf);
9922     while (*game == ' ') game++;
9923     p = buf;
9924     while (*game != ' ' && *game != '\n') *p++ = *game++;
9925     *p = NULLCHAR;
9926     gameInfo.black = StrSave(buf);
9927
9928     /* Parse moves */
9929     boardIndex = blackPlaysFirst ? 1 : 0;
9930     yynewstr(game);
9931     for (;;) {
9932         yyboardindex = boardIndex;
9933         moveType = (ChessMove) Myylex();
9934         switch (moveType) {
9935           case IllegalMove:             /* maybe suicide chess, etc. */
9936   if (appData.debugMode) {
9937     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9938     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9939     setbuf(debugFP, NULL);
9940   }
9941           case WhitePromotion:
9942           case BlackPromotion:
9943           case WhiteNonPromotion:
9944           case BlackNonPromotion:
9945           case NormalMove:
9946           case FirstLeg:
9947           case WhiteCapturesEnPassant:
9948           case BlackCapturesEnPassant:
9949           case WhiteKingSideCastle:
9950           case WhiteQueenSideCastle:
9951           case BlackKingSideCastle:
9952           case BlackQueenSideCastle:
9953           case WhiteKingSideCastleWild:
9954           case WhiteQueenSideCastleWild:
9955           case BlackKingSideCastleWild:
9956           case BlackQueenSideCastleWild:
9957           /* PUSH Fabien */
9958           case WhiteHSideCastleFR:
9959           case WhiteASideCastleFR:
9960           case BlackHSideCastleFR:
9961           case BlackASideCastleFR:
9962           /* POP Fabien */
9963             fromX = currentMoveString[0] - AAA;
9964             fromY = currentMoveString[1] - ONE;
9965             toX = currentMoveString[2] - AAA;
9966             toY = currentMoveString[3] - ONE;
9967             promoChar = currentMoveString[4];
9968             break;
9969           case WhiteDrop:
9970           case BlackDrop:
9971             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9972             fromX = moveType == WhiteDrop ?
9973               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9974             (int) CharToPiece(ToLower(currentMoveString[0]));
9975             fromY = DROP_RANK;
9976             toX = currentMoveString[2] - AAA;
9977             toY = currentMoveString[3] - ONE;
9978             promoChar = NULLCHAR;
9979             break;
9980           case AmbiguousMove:
9981             /* bug? */
9982             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9983   if (appData.debugMode) {
9984     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9985     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9986     setbuf(debugFP, NULL);
9987   }
9988             DisplayError(buf, 0);
9989             return;
9990           case ImpossibleMove:
9991             /* bug? */
9992             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9993   if (appData.debugMode) {
9994     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9995     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9996     setbuf(debugFP, NULL);
9997   }
9998             DisplayError(buf, 0);
9999             return;
10000           case EndOfFile:
10001             if (boardIndex < backwardMostMove) {
10002                 /* Oops, gap.  How did that happen? */
10003                 DisplayError(_("Gap in move list"), 0);
10004                 return;
10005             }
10006             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10007             if (boardIndex > forwardMostMove) {
10008                 forwardMostMove = boardIndex;
10009             }
10010             return;
10011           case ElapsedTime:
10012             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10013                 strcat(parseList[boardIndex-1], " ");
10014                 strcat(parseList[boardIndex-1], yy_text);
10015             }
10016             continue;
10017           case Comment:
10018           case PGNTag:
10019           case NAG:
10020           default:
10021             /* ignore */
10022             continue;
10023           case WhiteWins:
10024           case BlackWins:
10025           case GameIsDrawn:
10026           case GameUnfinished:
10027             if (gameMode == IcsExamining) {
10028                 if (boardIndex < backwardMostMove) {
10029                     /* Oops, gap.  How did that happen? */
10030                     return;
10031                 }
10032                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10033                 return;
10034             }
10035             gameInfo.result = moveType;
10036             p = strchr(yy_text, '{');
10037             if (p == NULL) p = strchr(yy_text, '(');
10038             if (p == NULL) {
10039                 p = yy_text;
10040                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10041             } else {
10042                 q = strchr(p, *p == '{' ? '}' : ')');
10043                 if (q != NULL) *q = NULLCHAR;
10044                 p++;
10045             }
10046             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10047             gameInfo.resultDetails = StrSave(p);
10048             continue;
10049         }
10050         if (boardIndex >= forwardMostMove &&
10051             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10052             backwardMostMove = blackPlaysFirst ? 1 : 0;
10053             return;
10054         }
10055         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10056                                  fromY, fromX, toY, toX, promoChar,
10057                                  parseList[boardIndex]);
10058         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10059         /* currentMoveString is set as a side-effect of yylex */
10060         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10061         strcat(moveList[boardIndex], "\n");
10062         boardIndex++;
10063         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10064         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10065           case MT_NONE:
10066           case MT_STALEMATE:
10067           default:
10068             break;
10069           case MT_CHECK:
10070             if(!IS_SHOGI(gameInfo.variant))
10071                 strcat(parseList[boardIndex - 1], "+");
10072             break;
10073           case MT_CHECKMATE:
10074           case MT_STAINMATE:
10075             strcat(parseList[boardIndex - 1], "#");
10076             break;
10077         }
10078     }
10079 }
10080
10081
10082 /* Apply a move to the given board  */
10083 void
10084 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10085 {
10086   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10087   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10088
10089     /* [HGM] compute & store e.p. status and castling rights for new position */
10090     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10091
10092       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10093       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10094       board[EP_STATUS] = EP_NONE;
10095       board[EP_FILE] = board[EP_RANK] = 100;
10096
10097   if (fromY == DROP_RANK) {
10098         /* must be first */
10099         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10100             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10101             return;
10102         }
10103         piece = board[toY][toX] = (ChessSquare) fromX;
10104   } else {
10105 //      ChessSquare victim;
10106       int i;
10107
10108       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10109 //           victim = board[killY][killX],
10110            killed = board[killY][killX],
10111            board[killY][killX] = EmptySquare,
10112            board[EP_STATUS] = EP_CAPTURE;
10113            if( kill2X >= 0 && kill2Y >= 0)
10114              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10115       }
10116
10117       if( board[toY][toX] != EmptySquare ) {
10118            board[EP_STATUS] = EP_CAPTURE;
10119            if( (fromX != toX || fromY != toY) && // not igui!
10120                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10121                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10122                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10123            }
10124       }
10125
10126       pawn = board[fromY][fromX];
10127       if( pawn == WhiteLance || pawn == BlackLance ) {
10128            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10129                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10130                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10131            }
10132       }
10133       if( pawn == WhitePawn ) {
10134            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10135                board[EP_STATUS] = EP_PAWN_MOVE;
10136            if( toY-fromY>=2) {
10137                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10138                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10139                         gameInfo.variant != VariantBerolina || toX < fromX)
10140                       board[EP_STATUS] = toX | berolina;
10141                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10142                         gameInfo.variant != VariantBerolina || toX > fromX)
10143                       board[EP_STATUS] = toX;
10144            }
10145       } else
10146       if( pawn == BlackPawn ) {
10147            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10148                board[EP_STATUS] = EP_PAWN_MOVE;
10149            if( toY-fromY<= -2) {
10150                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10151                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10152                         gameInfo.variant != VariantBerolina || toX < fromX)
10153                       board[EP_STATUS] = toX | berolina;
10154                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10155                         gameInfo.variant != VariantBerolina || toX > fromX)
10156                       board[EP_STATUS] = toX;
10157            }
10158        }
10159
10160        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10161        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10162        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10163        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10164
10165        for(i=0; i<nrCastlingRights; i++) {
10166            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10167               board[CASTLING][i] == toX   && castlingRank[i] == toY
10168              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10169        }
10170
10171        if(gameInfo.variant == VariantSChess) { // update virginity
10172            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10173            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10174            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10175            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10176        }
10177
10178      if (fromX == toX && fromY == toY) return;
10179
10180      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10181      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10182      if(gameInfo.variant == VariantKnightmate)
10183          king += (int) WhiteUnicorn - (int) WhiteKing;
10184
10185     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10186        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10187         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10188         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10189         board[EP_STATUS] = EP_NONE; // capture was fake!
10190     } else
10191     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10192         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10193         board[toY][toX] = piece;
10194         board[EP_STATUS] = EP_NONE; // capture was fake!
10195     } else
10196     /* Code added by Tord: */
10197     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10198     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10199         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10200       board[EP_STATUS] = EP_NONE; // capture was fake!
10201       board[fromY][fromX] = EmptySquare;
10202       board[toY][toX] = EmptySquare;
10203       if((toX > fromX) != (piece == WhiteRook)) {
10204         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10205       } else {
10206         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10207       }
10208     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10209                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10210       board[EP_STATUS] = EP_NONE;
10211       board[fromY][fromX] = EmptySquare;
10212       board[toY][toX] = EmptySquare;
10213       if((toX > fromX) != (piece == BlackRook)) {
10214         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10215       } else {
10216         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10217       }
10218     /* End of code added by Tord */
10219
10220     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10221         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10222         board[toY][toX] = piece;
10223     } else if (board[fromY][fromX] == king
10224         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10225         && toY == fromY && toX > fromX+1) {
10226         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10227         board[fromY][toX-1] = board[fromY][rookX];
10228         board[fromY][rookX] = EmptySquare;
10229         board[fromY][fromX] = EmptySquare;
10230         board[toY][toX] = king;
10231     } else if (board[fromY][fromX] == king
10232         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10233                && toY == fromY && toX < fromX-1) {
10234         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10235         board[fromY][toX+1] = board[fromY][rookX];
10236         board[fromY][rookX] = EmptySquare;
10237         board[fromY][fromX] = EmptySquare;
10238         board[toY][toX] = king;
10239     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10240                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10241                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10242                ) {
10243         /* white pawn promotion */
10244         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10245         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10246             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10247         board[fromY][fromX] = EmptySquare;
10248     } else if ((fromY >= BOARD_HEIGHT>>1)
10249                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10250                && (toX != fromX)
10251                && gameInfo.variant != VariantXiangqi
10252                && gameInfo.variant != VariantBerolina
10253                && (pawn == WhitePawn)
10254                && (board[toY][toX] == EmptySquare)) {
10255         board[fromY][fromX] = EmptySquare;
10256         board[toY][toX] = piece;
10257         if(toY == epRank - 128 + 1)
10258             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10259         else
10260             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10261     } else if ((fromY == BOARD_HEIGHT-4)
10262                && (toX == fromX)
10263                && gameInfo.variant == VariantBerolina
10264                && (board[fromY][fromX] == WhitePawn)
10265                && (board[toY][toX] == EmptySquare)) {
10266         board[fromY][fromX] = EmptySquare;
10267         board[toY][toX] = WhitePawn;
10268         if(oldEP & EP_BEROLIN_A) {
10269                 captured = board[fromY][fromX-1];
10270                 board[fromY][fromX-1] = EmptySquare;
10271         }else{  captured = board[fromY][fromX+1];
10272                 board[fromY][fromX+1] = EmptySquare;
10273         }
10274     } else if (board[fromY][fromX] == king
10275         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10276                && toY == fromY && toX > fromX+1) {
10277         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10278         board[fromY][toX-1] = board[fromY][rookX];
10279         board[fromY][rookX] = EmptySquare;
10280         board[fromY][fromX] = EmptySquare;
10281         board[toY][toX] = king;
10282     } else if (board[fromY][fromX] == king
10283         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10284                && toY == fromY && toX < fromX-1) {
10285         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10286         board[fromY][toX+1] = board[fromY][rookX];
10287         board[fromY][rookX] = EmptySquare;
10288         board[fromY][fromX] = EmptySquare;
10289         board[toY][toX] = king;
10290     } else if (fromY == 7 && fromX == 3
10291                && board[fromY][fromX] == BlackKing
10292                && toY == 7 && toX == 5) {
10293         board[fromY][fromX] = EmptySquare;
10294         board[toY][toX] = BlackKing;
10295         board[fromY][7] = EmptySquare;
10296         board[toY][4] = BlackRook;
10297     } else if (fromY == 7 && fromX == 3
10298                && board[fromY][fromX] == BlackKing
10299                && toY == 7 && toX == 1) {
10300         board[fromY][fromX] = EmptySquare;
10301         board[toY][toX] = BlackKing;
10302         board[fromY][0] = EmptySquare;
10303         board[toY][2] = BlackRook;
10304     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10305                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10306                && toY < promoRank && promoChar
10307                ) {
10308         /* black pawn promotion */
10309         board[toY][toX] = CharToPiece(ToLower(promoChar));
10310         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10311             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10312         board[fromY][fromX] = EmptySquare;
10313     } else if ((fromY < BOARD_HEIGHT>>1)
10314                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10315                && (toX != fromX)
10316                && gameInfo.variant != VariantXiangqi
10317                && gameInfo.variant != VariantBerolina
10318                && (pawn == BlackPawn)
10319                && (board[toY][toX] == EmptySquare)) {
10320         board[fromY][fromX] = EmptySquare;
10321         board[toY][toX] = piece;
10322         if(toY == epRank - 128 - 1)
10323             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10324         else
10325             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10326     } else if ((fromY == 3)
10327                && (toX == fromX)
10328                && gameInfo.variant == VariantBerolina
10329                && (board[fromY][fromX] == BlackPawn)
10330                && (board[toY][toX] == EmptySquare)) {
10331         board[fromY][fromX] = EmptySquare;
10332         board[toY][toX] = BlackPawn;
10333         if(oldEP & EP_BEROLIN_A) {
10334                 captured = board[fromY][fromX-1];
10335                 board[fromY][fromX-1] = EmptySquare;
10336         }else{  captured = board[fromY][fromX+1];
10337                 board[fromY][fromX+1] = EmptySquare;
10338         }
10339     } else {
10340         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10341         board[fromY][fromX] = EmptySquare;
10342         board[toY][toX] = piece;
10343     }
10344   }
10345
10346     if (gameInfo.holdingsWidth != 0) {
10347
10348       /* !!A lot more code needs to be written to support holdings  */
10349       /* [HGM] OK, so I have written it. Holdings are stored in the */
10350       /* penultimate board files, so they are automaticlly stored   */
10351       /* in the game history.                                       */
10352       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10353                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10354         /* Delete from holdings, by decreasing count */
10355         /* and erasing image if necessary            */
10356         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10357         if(p < (int) BlackPawn) { /* white drop */
10358              p -= (int)WhitePawn;
10359                  p = PieceToNumber((ChessSquare)p);
10360              if(p >= gameInfo.holdingsSize) p = 0;
10361              if(--board[p][BOARD_WIDTH-2] <= 0)
10362                   board[p][BOARD_WIDTH-1] = EmptySquare;
10363              if((int)board[p][BOARD_WIDTH-2] < 0)
10364                         board[p][BOARD_WIDTH-2] = 0;
10365         } else {                  /* black drop */
10366              p -= (int)BlackPawn;
10367                  p = PieceToNumber((ChessSquare)p);
10368              if(p >= gameInfo.holdingsSize) p = 0;
10369              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10370                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10371              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10372                         board[BOARD_HEIGHT-1-p][1] = 0;
10373         }
10374       }
10375       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10376           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10377         /* [HGM] holdings: Add to holdings, if holdings exist */
10378         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10379                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10380                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10381         }
10382         p = (int) captured;
10383         if (p >= (int) BlackPawn) {
10384           p -= (int)BlackPawn;
10385           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10386                   /* Restore shogi-promoted piece to its original  first */
10387                   captured = (ChessSquare) (DEMOTED(captured));
10388                   p = DEMOTED(p);
10389           }
10390           p = PieceToNumber((ChessSquare)p);
10391           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10392           board[p][BOARD_WIDTH-2]++;
10393           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10394         } else {
10395           p -= (int)WhitePawn;
10396           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10397                   captured = (ChessSquare) (DEMOTED(captured));
10398                   p = DEMOTED(p);
10399           }
10400           p = PieceToNumber((ChessSquare)p);
10401           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10402           board[BOARD_HEIGHT-1-p][1]++;
10403           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10404         }
10405       }
10406     } else if (gameInfo.variant == VariantAtomic) {
10407       if (captured != EmptySquare) {
10408         int y, x;
10409         for (y = toY-1; y <= toY+1; y++) {
10410           for (x = toX-1; x <= toX+1; x++) {
10411             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10412                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10413               board[y][x] = EmptySquare;
10414             }
10415           }
10416         }
10417         board[toY][toX] = EmptySquare;
10418       }
10419     }
10420
10421     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10422         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10423     } else
10424     if(promoChar == '+') {
10425         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10426         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10427         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10428           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10429     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10430         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10431         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10432            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10433         board[toY][toX] = newPiece;
10434     }
10435     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10436                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10437         // [HGM] superchess: take promotion piece out of holdings
10438         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10439         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10440             if(!--board[k][BOARD_WIDTH-2])
10441                 board[k][BOARD_WIDTH-1] = EmptySquare;
10442         } else {
10443             if(!--board[BOARD_HEIGHT-1-k][1])
10444                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10445         }
10446     }
10447 }
10448
10449 /* Updates forwardMostMove */
10450 void
10451 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10452 {
10453     int x = toX, y = toY;
10454     char *s = parseList[forwardMostMove];
10455     ChessSquare p = boards[forwardMostMove][toY][toX];
10456 //    forwardMostMove++; // [HGM] bare: moved downstream
10457
10458     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10459     (void) CoordsToAlgebraic(boards[forwardMostMove],
10460                              PosFlags(forwardMostMove),
10461                              fromY, fromX, y, x, promoChar,
10462                              s);
10463     if(killX >= 0 && killY >= 0)
10464         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10465
10466     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10467         int timeLeft; static int lastLoadFlag=0; int king, piece;
10468         piece = boards[forwardMostMove][fromY][fromX];
10469         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10470         if(gameInfo.variant == VariantKnightmate)
10471             king += (int) WhiteUnicorn - (int) WhiteKing;
10472         if(forwardMostMove == 0) {
10473             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10474                 fprintf(serverMoves, "%s;", UserName());
10475             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10476                 fprintf(serverMoves, "%s;", second.tidy);
10477             fprintf(serverMoves, "%s;", first.tidy);
10478             if(gameMode == MachinePlaysWhite)
10479                 fprintf(serverMoves, "%s;", UserName());
10480             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10481                 fprintf(serverMoves, "%s;", second.tidy);
10482         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10483         lastLoadFlag = loadFlag;
10484         // print base move
10485         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10486         // print castling suffix
10487         if( toY == fromY && piece == king ) {
10488             if(toX-fromX > 1)
10489                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10490             if(fromX-toX >1)
10491                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10492         }
10493         // e.p. suffix
10494         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10495              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10496              boards[forwardMostMove][toY][toX] == EmptySquare
10497              && fromX != toX && fromY != toY)
10498                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10499         // promotion suffix
10500         if(promoChar != NULLCHAR) {
10501             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10502                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10503                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10504             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10505         }
10506         if(!loadFlag) {
10507                 char buf[MOVE_LEN*2], *p; int len;
10508             fprintf(serverMoves, "/%d/%d",
10509                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10510             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10511             else                      timeLeft = blackTimeRemaining/1000;
10512             fprintf(serverMoves, "/%d", timeLeft);
10513                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10514                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10515                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10516                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10517             fprintf(serverMoves, "/%s", buf);
10518         }
10519         fflush(serverMoves);
10520     }
10521
10522     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10523         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10524       return;
10525     }
10526     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10527     if (commentList[forwardMostMove+1] != NULL) {
10528         free(commentList[forwardMostMove+1]);
10529         commentList[forwardMostMove+1] = NULL;
10530     }
10531     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10532     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10533     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10534     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10535     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10536     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10537     adjustedClock = FALSE;
10538     gameInfo.result = GameUnfinished;
10539     if (gameInfo.resultDetails != NULL) {
10540         free(gameInfo.resultDetails);
10541         gameInfo.resultDetails = NULL;
10542     }
10543     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10544                               moveList[forwardMostMove - 1]);
10545     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10546       case MT_NONE:
10547       case MT_STALEMATE:
10548       default:
10549         break;
10550       case MT_CHECK:
10551         if(!IS_SHOGI(gameInfo.variant))
10552             strcat(parseList[forwardMostMove - 1], "+");
10553         break;
10554       case MT_CHECKMATE:
10555       case MT_STAINMATE:
10556         strcat(parseList[forwardMostMove - 1], "#");
10557         break;
10558     }
10559 }
10560
10561 /* Updates currentMove if not pausing */
10562 void
10563 ShowMove (int fromX, int fromY, int toX, int toY)
10564 {
10565     int instant = (gameMode == PlayFromGameFile) ?
10566         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10567     if(appData.noGUI) return;
10568     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10569         if (!instant) {
10570             if (forwardMostMove == currentMove + 1) {
10571                 AnimateMove(boards[forwardMostMove - 1],
10572                             fromX, fromY, toX, toY);
10573             }
10574         }
10575         currentMove = forwardMostMove;
10576     }
10577
10578     killX = killY = -1; // [HGM] lion: used up
10579
10580     if (instant) return;
10581
10582     DisplayMove(currentMove - 1);
10583     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10584             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10585                 SetHighlights(fromX, fromY, toX, toY);
10586             }
10587     }
10588     DrawPosition(FALSE, boards[currentMove]);
10589     DisplayBothClocks();
10590     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10591 }
10592
10593 void
10594 SendEgtPath (ChessProgramState *cps)
10595 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10596         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10597
10598         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10599
10600         while(*p) {
10601             char c, *q = name+1, *r, *s;
10602
10603             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10604             while(*p && *p != ',') *q++ = *p++;
10605             *q++ = ':'; *q = 0;
10606             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10607                 strcmp(name, ",nalimov:") == 0 ) {
10608                 // take nalimov path from the menu-changeable option first, if it is defined
10609               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10610                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10611             } else
10612             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10613                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10614                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10615                 s = r = StrStr(s, ":") + 1; // beginning of path info
10616                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10617                 c = *r; *r = 0;             // temporarily null-terminate path info
10618                     *--q = 0;               // strip of trailig ':' from name
10619                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10620                 *r = c;
10621                 SendToProgram(buf,cps);     // send egtbpath command for this format
10622             }
10623             if(*p == ',') p++; // read away comma to position for next format name
10624         }
10625 }
10626
10627 static int
10628 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10629 {
10630       int width = 8, height = 8, holdings = 0;             // most common sizes
10631       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10632       // correct the deviations default for each variant
10633       if( v == VariantXiangqi ) width = 9,  height = 10;
10634       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10635       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10636       if( v == VariantCapablanca || v == VariantCapaRandom ||
10637           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10638                                 width = 10;
10639       if( v == VariantCourier ) width = 12;
10640       if( v == VariantSuper )                            holdings = 8;
10641       if( v == VariantGreat )   width = 10,              holdings = 8;
10642       if( v == VariantSChess )                           holdings = 7;
10643       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10644       if( v == VariantChuChess) width = 10, height = 10;
10645       if( v == VariantChu )     width = 12, height = 12;
10646       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10647              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10648              holdingsSize >= 0 && holdingsSize != holdings;
10649 }
10650
10651 char variantError[MSG_SIZ];
10652
10653 char *
10654 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10655 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10656       char *p, *variant = VariantName(v);
10657       static char b[MSG_SIZ];
10658       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10659            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10660                                                holdingsSize, variant); // cook up sized variant name
10661            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10662            if(StrStr(list, b) == NULL) {
10663                // specific sized variant not known, check if general sizing allowed
10664                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10665                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10666                             boardWidth, boardHeight, holdingsSize, engine);
10667                    return NULL;
10668                }
10669                /* [HGM] here we really should compare with the maximum supported board size */
10670            }
10671       } else snprintf(b, MSG_SIZ,"%s", variant);
10672       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10673       p = StrStr(list, b);
10674       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10675       if(p == NULL) {
10676           // occurs not at all in list, or only as sub-string
10677           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10678           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10679               int l = strlen(variantError);
10680               char *q;
10681               while(p != list && p[-1] != ',') p--;
10682               q = strchr(p, ',');
10683               if(q) *q = NULLCHAR;
10684               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10685               if(q) *q= ',';
10686           }
10687           return NULL;
10688       }
10689       return b;
10690 }
10691
10692 void
10693 InitChessProgram (ChessProgramState *cps, int setup)
10694 /* setup needed to setup FRC opening position */
10695 {
10696     char buf[MSG_SIZ], *b;
10697     if (appData.noChessProgram) return;
10698     hintRequested = FALSE;
10699     bookRequested = FALSE;
10700
10701     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10702     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10703     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10704     if(cps->memSize) { /* [HGM] memory */
10705       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10706         SendToProgram(buf, cps);
10707     }
10708     SendEgtPath(cps); /* [HGM] EGT */
10709     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10710       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10711         SendToProgram(buf, cps);
10712     }
10713
10714     setboardSpoiledMachineBlack = FALSE;
10715     SendToProgram(cps->initString, cps);
10716     if (gameInfo.variant != VariantNormal &&
10717         gameInfo.variant != VariantLoadable
10718         /* [HGM] also send variant if board size non-standard */
10719         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10720
10721       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10722                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10723       if (b == NULL) {
10724         VariantClass v;
10725         char c, *q = cps->variants, *p = strchr(q, ',');
10726         if(p) *p = NULLCHAR;
10727         v = StringToVariant(q);
10728         DisplayError(variantError, 0);
10729         if(v != VariantUnknown && cps == &first) {
10730             int w, h, s;
10731             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10732                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10733             ASSIGN(appData.variant, q);
10734             Reset(TRUE, FALSE);
10735         }
10736         if(p) *p = ',';
10737         return;
10738       }
10739
10740       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10741       SendToProgram(buf, cps);
10742     }
10743     currentlyInitializedVariant = gameInfo.variant;
10744
10745     /* [HGM] send opening position in FRC to first engine */
10746     if(setup) {
10747           SendToProgram("force\n", cps);
10748           SendBoard(cps, 0);
10749           /* engine is now in force mode! Set flag to wake it up after first move. */
10750           setboardSpoiledMachineBlack = 1;
10751     }
10752
10753     if (cps->sendICS) {
10754       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10755       SendToProgram(buf, cps);
10756     }
10757     cps->maybeThinking = FALSE;
10758     cps->offeredDraw = 0;
10759     if (!appData.icsActive) {
10760         SendTimeControl(cps, movesPerSession, timeControl,
10761                         timeIncrement, appData.searchDepth,
10762                         searchTime);
10763     }
10764     if (appData.showThinking
10765         // [HGM] thinking: four options require thinking output to be sent
10766         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10767                                 ) {
10768         SendToProgram("post\n", cps);
10769     }
10770     SendToProgram("hard\n", cps);
10771     if (!appData.ponderNextMove) {
10772         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10773            it without being sure what state we are in first.  "hard"
10774            is not a toggle, so that one is OK.
10775          */
10776         SendToProgram("easy\n", cps);
10777     }
10778     if (cps->usePing) {
10779       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10780       SendToProgram(buf, cps);
10781     }
10782     cps->initDone = TRUE;
10783     ClearEngineOutputPane(cps == &second);
10784 }
10785
10786
10787 void
10788 ResendOptions (ChessProgramState *cps)
10789 { // send the stored value of the options
10790   int i;
10791   char buf[MSG_SIZ];
10792   Option *opt = cps->option;
10793   for(i=0; i<cps->nrOptions; i++, opt++) {
10794       switch(opt->type) {
10795         case Spin:
10796         case Slider:
10797         case CheckBox:
10798             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10799           break;
10800         case ComboBox:
10801           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10802           break;
10803         default:
10804             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10805           break;
10806         case Button:
10807         case SaveButton:
10808           continue;
10809       }
10810       SendToProgram(buf, cps);
10811   }
10812 }
10813
10814 void
10815 StartChessProgram (ChessProgramState *cps)
10816 {
10817     char buf[MSG_SIZ];
10818     int err;
10819
10820     if (appData.noChessProgram) return;
10821     cps->initDone = FALSE;
10822
10823     if (strcmp(cps->host, "localhost") == 0) {
10824         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10825     } else if (*appData.remoteShell == NULLCHAR) {
10826         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10827     } else {
10828         if (*appData.remoteUser == NULLCHAR) {
10829           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10830                     cps->program);
10831         } else {
10832           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10833                     cps->host, appData.remoteUser, cps->program);
10834         }
10835         err = StartChildProcess(buf, "", &cps->pr);
10836     }
10837
10838     if (err != 0) {
10839       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10840         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10841         if(cps != &first) return;
10842         appData.noChessProgram = TRUE;
10843         ThawUI();
10844         SetNCPMode();
10845 //      DisplayFatalError(buf, err, 1);
10846 //      cps->pr = NoProc;
10847 //      cps->isr = NULL;
10848         return;
10849     }
10850
10851     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10852     if (cps->protocolVersion > 1) {
10853       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10854       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10855         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10856         cps->comboCnt = 0;  //                and values of combo boxes
10857       }
10858       SendToProgram(buf, cps);
10859       if(cps->reload) ResendOptions(cps);
10860     } else {
10861       SendToProgram("xboard\n", cps);
10862     }
10863 }
10864
10865 void
10866 TwoMachinesEventIfReady P((void))
10867 {
10868   static int curMess = 0;
10869   if (first.lastPing != first.lastPong) {
10870     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10871     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10872     return;
10873   }
10874   if (second.lastPing != second.lastPong) {
10875     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10876     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10877     return;
10878   }
10879   DisplayMessage("", ""); curMess = 0;
10880   TwoMachinesEvent();
10881 }
10882
10883 char *
10884 MakeName (char *template)
10885 {
10886     time_t clock;
10887     struct tm *tm;
10888     static char buf[MSG_SIZ];
10889     char *p = buf;
10890     int i;
10891
10892     clock = time((time_t *)NULL);
10893     tm = localtime(&clock);
10894
10895     while(*p++ = *template++) if(p[-1] == '%') {
10896         switch(*template++) {
10897           case 0:   *p = 0; return buf;
10898           case 'Y': i = tm->tm_year+1900; break;
10899           case 'y': i = tm->tm_year-100; break;
10900           case 'M': i = tm->tm_mon+1; break;
10901           case 'd': i = tm->tm_mday; break;
10902           case 'h': i = tm->tm_hour; break;
10903           case 'm': i = tm->tm_min; break;
10904           case 's': i = tm->tm_sec; break;
10905           default:  i = 0;
10906         }
10907         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10908     }
10909     return buf;
10910 }
10911
10912 int
10913 CountPlayers (char *p)
10914 {
10915     int n = 0;
10916     while(p = strchr(p, '\n')) p++, n++; // count participants
10917     return n;
10918 }
10919
10920 FILE *
10921 WriteTourneyFile (char *results, FILE *f)
10922 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10923     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10924     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10925         // create a file with tournament description
10926         fprintf(f, "-participants {%s}\n", appData.participants);
10927         fprintf(f, "-seedBase %d\n", appData.seedBase);
10928         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10929         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10930         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10931         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10932         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10933         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10934         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10935         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10936         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10937         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10938         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10939         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10940         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10941         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10942         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10943         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10944         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10945         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10946         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10947         fprintf(f, "-smpCores %d\n", appData.smpCores);
10948         if(searchTime > 0)
10949                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10950         else {
10951                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10952                 fprintf(f, "-tc %s\n", appData.timeControl);
10953                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10954         }
10955         fprintf(f, "-results \"%s\"\n", results);
10956     }
10957     return f;
10958 }
10959
10960 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10961
10962 void
10963 Substitute (char *participants, int expunge)
10964 {
10965     int i, changed, changes=0, nPlayers=0;
10966     char *p, *q, *r, buf[MSG_SIZ];
10967     if(participants == NULL) return;
10968     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10969     r = p = participants; q = appData.participants;
10970     while(*p && *p == *q) {
10971         if(*p == '\n') r = p+1, nPlayers++;
10972         p++; q++;
10973     }
10974     if(*p) { // difference
10975         while(*p && *p++ != '\n');
10976         while(*q && *q++ != '\n');
10977       changed = nPlayers;
10978         changes = 1 + (strcmp(p, q) != 0);
10979     }
10980     if(changes == 1) { // a single engine mnemonic was changed
10981         q = r; while(*q) nPlayers += (*q++ == '\n');
10982         p = buf; while(*r && (*p = *r++) != '\n') p++;
10983         *p = NULLCHAR;
10984         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10985         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10986         if(mnemonic[i]) { // The substitute is valid
10987             FILE *f;
10988             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10989                 flock(fileno(f), LOCK_EX);
10990                 ParseArgsFromFile(f);
10991                 fseek(f, 0, SEEK_SET);
10992                 FREE(appData.participants); appData.participants = participants;
10993                 if(expunge) { // erase results of replaced engine
10994                     int len = strlen(appData.results), w, b, dummy;
10995                     for(i=0; i<len; i++) {
10996                         Pairing(i, nPlayers, &w, &b, &dummy);
10997                         if((w == changed || b == changed) && appData.results[i] == '*') {
10998                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10999                             fclose(f);
11000                             return;
11001                         }
11002                     }
11003                     for(i=0; i<len; i++) {
11004                         Pairing(i, nPlayers, &w, &b, &dummy);
11005                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11006                     }
11007                 }
11008                 WriteTourneyFile(appData.results, f);
11009                 fclose(f); // release lock
11010                 return;
11011             }
11012         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11013     }
11014     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11015     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11016     free(participants);
11017     return;
11018 }
11019
11020 int
11021 CheckPlayers (char *participants)
11022 {
11023         int i;
11024         char buf[MSG_SIZ], *p;
11025         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11026         while(p = strchr(participants, '\n')) {
11027             *p = NULLCHAR;
11028             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11029             if(!mnemonic[i]) {
11030                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11031                 *p = '\n';
11032                 DisplayError(buf, 0);
11033                 return 1;
11034             }
11035             *p = '\n';
11036             participants = p + 1;
11037         }
11038         return 0;
11039 }
11040
11041 int
11042 CreateTourney (char *name)
11043 {
11044         FILE *f;
11045         if(matchMode && strcmp(name, appData.tourneyFile)) {
11046              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11047         }
11048         if(name[0] == NULLCHAR) {
11049             if(appData.participants[0])
11050                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11051             return 0;
11052         }
11053         f = fopen(name, "r");
11054         if(f) { // file exists
11055             ASSIGN(appData.tourneyFile, name);
11056             ParseArgsFromFile(f); // parse it
11057         } else {
11058             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11059             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11060                 DisplayError(_("Not enough participants"), 0);
11061                 return 0;
11062             }
11063             if(CheckPlayers(appData.participants)) return 0;
11064             ASSIGN(appData.tourneyFile, name);
11065             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11066             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11067         }
11068         fclose(f);
11069         appData.noChessProgram = FALSE;
11070         appData.clockMode = TRUE;
11071         SetGNUMode();
11072         return 1;
11073 }
11074
11075 int
11076 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11077 {
11078     char buf[MSG_SIZ], *p, *q;
11079     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11080     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11081     skip = !all && group[0]; // if group requested, we start in skip mode
11082     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11083         p = names; q = buf; header = 0;
11084         while(*p && *p != '\n') *q++ = *p++;
11085         *q = 0;
11086         if(*p == '\n') p++;
11087         if(buf[0] == '#') {
11088             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11089             depth++; // we must be entering a new group
11090             if(all) continue; // suppress printing group headers when complete list requested
11091             header = 1;
11092             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11093         }
11094         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11095         if(engineList[i]) free(engineList[i]);
11096         engineList[i] = strdup(buf);
11097         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11098         if(engineMnemonic[i]) free(engineMnemonic[i]);
11099         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11100             strcat(buf, " (");
11101             sscanf(q + 8, "%s", buf + strlen(buf));
11102             strcat(buf, ")");
11103         }
11104         engineMnemonic[i] = strdup(buf);
11105         i++;
11106     }
11107     engineList[i] = engineMnemonic[i] = NULL;
11108     return i;
11109 }
11110
11111 // following implemented as macro to avoid type limitations
11112 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11113
11114 void
11115 SwapEngines (int n)
11116 {   // swap settings for first engine and other engine (so far only some selected options)
11117     int h;
11118     char *p;
11119     if(n == 0) return;
11120     SWAP(directory, p)
11121     SWAP(chessProgram, p)
11122     SWAP(isUCI, h)
11123     SWAP(hasOwnBookUCI, h)
11124     SWAP(protocolVersion, h)
11125     SWAP(reuse, h)
11126     SWAP(scoreIsAbsolute, h)
11127     SWAP(timeOdds, h)
11128     SWAP(logo, p)
11129     SWAP(pgnName, p)
11130     SWAP(pvSAN, h)
11131     SWAP(engOptions, p)
11132     SWAP(engInitString, p)
11133     SWAP(computerString, p)
11134     SWAP(features, p)
11135     SWAP(fenOverride, p)
11136     SWAP(NPS, h)
11137     SWAP(accumulateTC, h)
11138     SWAP(drawDepth, h)
11139     SWAP(host, p)
11140     SWAP(pseudo, h)
11141 }
11142
11143 int
11144 GetEngineLine (char *s, int n)
11145 {
11146     int i;
11147     char buf[MSG_SIZ];
11148     extern char *icsNames;
11149     if(!s || !*s) return 0;
11150     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11151     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11152     if(!mnemonic[i]) return 0;
11153     if(n == 11) return 1; // just testing if there was a match
11154     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11155     if(n == 1) SwapEngines(n);
11156     ParseArgsFromString(buf);
11157     if(n == 1) SwapEngines(n);
11158     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11159         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11160         ParseArgsFromString(buf);
11161     }
11162     return 1;
11163 }
11164
11165 int
11166 SetPlayer (int player, char *p)
11167 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11168     int i;
11169     char buf[MSG_SIZ], *engineName;
11170     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11171     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11172     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11173     if(mnemonic[i]) {
11174         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11175         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11176         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11177         ParseArgsFromString(buf);
11178     } else { // no engine with this nickname is installed!
11179         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11180         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11181         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11182         ModeHighlight();
11183         DisplayError(buf, 0);
11184         return 0;
11185     }
11186     free(engineName);
11187     return i;
11188 }
11189
11190 char *recentEngines;
11191
11192 void
11193 RecentEngineEvent (int nr)
11194 {
11195     int n;
11196 //    SwapEngines(1); // bump first to second
11197 //    ReplaceEngine(&second, 1); // and load it there
11198     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11199     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11200     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11201         ReplaceEngine(&first, 0);
11202         FloatToFront(&appData.recentEngineList, command[n]);
11203     }
11204 }
11205
11206 int
11207 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11208 {   // determine players from game number
11209     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11210
11211     if(appData.tourneyType == 0) {
11212         roundsPerCycle = (nPlayers - 1) | 1;
11213         pairingsPerRound = nPlayers / 2;
11214     } else if(appData.tourneyType > 0) {
11215         roundsPerCycle = nPlayers - appData.tourneyType;
11216         pairingsPerRound = appData.tourneyType;
11217     }
11218     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11219     gamesPerCycle = gamesPerRound * roundsPerCycle;
11220     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11221     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11222     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11223     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11224     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11225     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11226
11227     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11228     if(appData.roundSync) *syncInterval = gamesPerRound;
11229
11230     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11231
11232     if(appData.tourneyType == 0) {
11233         if(curPairing == (nPlayers-1)/2 ) {
11234             *whitePlayer = curRound;
11235             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11236         } else {
11237             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11238             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11239             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11240             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11241         }
11242     } else if(appData.tourneyType > 1) {
11243         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11244         *whitePlayer = curRound + appData.tourneyType;
11245     } else if(appData.tourneyType > 0) {
11246         *whitePlayer = curPairing;
11247         *blackPlayer = curRound + appData.tourneyType;
11248     }
11249
11250     // take care of white/black alternation per round.
11251     // For cycles and games this is already taken care of by default, derived from matchGame!
11252     return curRound & 1;
11253 }
11254
11255 int
11256 NextTourneyGame (int nr, int *swapColors)
11257 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11258     char *p, *q;
11259     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11260     FILE *tf;
11261     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11262     tf = fopen(appData.tourneyFile, "r");
11263     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11264     ParseArgsFromFile(tf); fclose(tf);
11265     InitTimeControls(); // TC might be altered from tourney file
11266
11267     nPlayers = CountPlayers(appData.participants); // count participants
11268     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11269     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11270
11271     if(syncInterval) {
11272         p = q = appData.results;
11273         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11274         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11275             DisplayMessage(_("Waiting for other game(s)"),"");
11276             waitingForGame = TRUE;
11277             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11278             return 0;
11279         }
11280         waitingForGame = FALSE;
11281     }
11282
11283     if(appData.tourneyType < 0) {
11284         if(nr>=0 && !pairingReceived) {
11285             char buf[1<<16];
11286             if(pairing.pr == NoProc) {
11287                 if(!appData.pairingEngine[0]) {
11288                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11289                     return 0;
11290                 }
11291                 StartChessProgram(&pairing); // starts the pairing engine
11292             }
11293             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11294             SendToProgram(buf, &pairing);
11295             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11296             SendToProgram(buf, &pairing);
11297             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11298         }
11299         pairingReceived = 0;                              // ... so we continue here
11300         *swapColors = 0;
11301         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11302         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11303         matchGame = 1; roundNr = nr / syncInterval + 1;
11304     }
11305
11306     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11307
11308     // redefine engines, engine dir, etc.
11309     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11310     if(first.pr == NoProc) {
11311       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11312       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11313     }
11314     if(second.pr == NoProc) {
11315       SwapEngines(1);
11316       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11317       SwapEngines(1);         // and make that valid for second engine by swapping
11318       InitEngine(&second, 1);
11319     }
11320     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11321     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11322     return OK;
11323 }
11324
11325 void
11326 NextMatchGame ()
11327 {   // performs game initialization that does not invoke engines, and then tries to start the game
11328     int res, firstWhite, swapColors = 0;
11329     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11330     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
11331         char buf[MSG_SIZ];
11332         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11333         if(strcmp(buf, currentDebugFile)) { // name has changed
11334             FILE *f = fopen(buf, "w");
11335             if(f) { // if opening the new file failed, just keep using the old one
11336                 ASSIGN(currentDebugFile, buf);
11337                 fclose(debugFP);
11338                 debugFP = f;
11339             }
11340             if(appData.serverFileName) {
11341                 if(serverFP) fclose(serverFP);
11342                 serverFP = fopen(appData.serverFileName, "w");
11343                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11344                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11345             }
11346         }
11347     }
11348     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11349     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11350     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11351     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11352     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11353     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11354     Reset(FALSE, first.pr != NoProc);
11355     res = LoadGameOrPosition(matchGame); // setup game
11356     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11357     if(!res) return; // abort when bad game/pos file
11358     TwoMachinesEvent();
11359 }
11360
11361 void
11362 UserAdjudicationEvent (int result)
11363 {
11364     ChessMove gameResult = GameIsDrawn;
11365
11366     if( result > 0 ) {
11367         gameResult = WhiteWins;
11368     }
11369     else if( result < 0 ) {
11370         gameResult = BlackWins;
11371     }
11372
11373     if( gameMode == TwoMachinesPlay ) {
11374         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11375     }
11376 }
11377
11378
11379 // [HGM] save: calculate checksum of game to make games easily identifiable
11380 int
11381 StringCheckSum (char *s)
11382 {
11383         int i = 0;
11384         if(s==NULL) return 0;
11385         while(*s) i = i*259 + *s++;
11386         return i;
11387 }
11388
11389 int
11390 GameCheckSum ()
11391 {
11392         int i, sum=0;
11393         for(i=backwardMostMove; i<forwardMostMove; i++) {
11394                 sum += pvInfoList[i].depth;
11395                 sum += StringCheckSum(parseList[i]);
11396                 sum += StringCheckSum(commentList[i]);
11397                 sum *= 261;
11398         }
11399         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11400         return sum + StringCheckSum(commentList[i]);
11401 } // end of save patch
11402
11403 void
11404 GameEnds (ChessMove result, char *resultDetails, int whosays)
11405 {
11406     GameMode nextGameMode;
11407     int isIcsGame;
11408     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11409
11410     if(endingGame) return; /* [HGM] crash: forbid recursion */
11411     endingGame = 1;
11412     if(twoBoards) { // [HGM] dual: switch back to one board
11413         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11414         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11415     }
11416     if (appData.debugMode) {
11417       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11418               result, resultDetails ? resultDetails : "(null)", whosays);
11419     }
11420
11421     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11422
11423     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11424
11425     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11426         /* If we are playing on ICS, the server decides when the
11427            game is over, but the engine can offer to draw, claim
11428            a draw, or resign.
11429          */
11430 #if ZIPPY
11431         if (appData.zippyPlay && first.initDone) {
11432             if (result == GameIsDrawn) {
11433                 /* In case draw still needs to be claimed */
11434                 SendToICS(ics_prefix);
11435                 SendToICS("draw\n");
11436             } else if (StrCaseStr(resultDetails, "resign")) {
11437                 SendToICS(ics_prefix);
11438                 SendToICS("resign\n");
11439             }
11440         }
11441 #endif
11442         endingGame = 0; /* [HGM] crash */
11443         return;
11444     }
11445
11446     /* If we're loading the game from a file, stop */
11447     if (whosays == GE_FILE) {
11448       (void) StopLoadGameTimer();
11449       gameFileFP = NULL;
11450     }
11451
11452     /* Cancel draw offers */
11453     first.offeredDraw = second.offeredDraw = 0;
11454
11455     /* If this is an ICS game, only ICS can really say it's done;
11456        if not, anyone can. */
11457     isIcsGame = (gameMode == IcsPlayingWhite ||
11458                  gameMode == IcsPlayingBlack ||
11459                  gameMode == IcsObserving    ||
11460                  gameMode == IcsExamining);
11461
11462     if (!isIcsGame || whosays == GE_ICS) {
11463         /* OK -- not an ICS game, or ICS said it was done */
11464         StopClocks();
11465         if (!isIcsGame && !appData.noChessProgram)
11466           SetUserThinkingEnables();
11467
11468         /* [HGM] if a machine claims the game end we verify this claim */
11469         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11470             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11471                 char claimer;
11472                 ChessMove trueResult = (ChessMove) -1;
11473
11474                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11475                                             first.twoMachinesColor[0] :
11476                                             second.twoMachinesColor[0] ;
11477
11478                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11479                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11480                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11481                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11482                 } else
11483                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11484                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11485                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11486                 } else
11487                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11488                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11489                 }
11490
11491                 // now verify win claims, but not in drop games, as we don't understand those yet
11492                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11493                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11494                     (result == WhiteWins && claimer == 'w' ||
11495                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11496                       if (appData.debugMode) {
11497                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11498                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11499                       }
11500                       if(result != trueResult) {
11501                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11502                               result = claimer == 'w' ? BlackWins : WhiteWins;
11503                               resultDetails = buf;
11504                       }
11505                 } else
11506                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11507                     && (forwardMostMove <= backwardMostMove ||
11508                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11509                         (claimer=='b')==(forwardMostMove&1))
11510                                                                                   ) {
11511                       /* [HGM] verify: draws that were not flagged are false claims */
11512                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11513                       result = claimer == 'w' ? BlackWins : WhiteWins;
11514                       resultDetails = buf;
11515                 }
11516                 /* (Claiming a loss is accepted no questions asked!) */
11517             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11518                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11519                 result = GameUnfinished;
11520                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11521             }
11522             /* [HGM] bare: don't allow bare King to win */
11523             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11524                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11525                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11526                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11527                && result != GameIsDrawn)
11528             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11529                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11530                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11531                         if(p >= 0 && p <= (int)WhiteKing) k++;
11532                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11533                 }
11534                 if (appData.debugMode) {
11535                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11536                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11537                 }
11538                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11539                         result = GameIsDrawn;
11540                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11541                         resultDetails = buf;
11542                 }
11543             }
11544         }
11545
11546
11547         if(serverMoves != NULL && !loadFlag) { char c = '=';
11548             if(result==WhiteWins) c = '+';
11549             if(result==BlackWins) c = '-';
11550             if(resultDetails != NULL)
11551                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11552         }
11553         if (resultDetails != NULL) {
11554             gameInfo.result = result;
11555             gameInfo.resultDetails = StrSave(resultDetails);
11556
11557             /* display last move only if game was not loaded from file */
11558             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11559                 DisplayMove(currentMove - 1);
11560
11561             if (forwardMostMove != 0) {
11562                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11563                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11564                                                                 ) {
11565                     if (*appData.saveGameFile != NULLCHAR) {
11566                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11567                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11568                         else
11569                         SaveGameToFile(appData.saveGameFile, TRUE);
11570                     } else if (appData.autoSaveGames) {
11571                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11572                     }
11573                     if (*appData.savePositionFile != NULLCHAR) {
11574                         SavePositionToFile(appData.savePositionFile);
11575                     }
11576                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11577                 }
11578             }
11579
11580             /* Tell program how game ended in case it is learning */
11581             /* [HGM] Moved this to after saving the PGN, just in case */
11582             /* engine died and we got here through time loss. In that */
11583             /* case we will get a fatal error writing the pipe, which */
11584             /* would otherwise lose us the PGN.                       */
11585             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11586             /* output during GameEnds should never be fatal anymore   */
11587             if (gameMode == MachinePlaysWhite ||
11588                 gameMode == MachinePlaysBlack ||
11589                 gameMode == TwoMachinesPlay ||
11590                 gameMode == IcsPlayingWhite ||
11591                 gameMode == IcsPlayingBlack ||
11592                 gameMode == BeginningOfGame) {
11593                 char buf[MSG_SIZ];
11594                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11595                         resultDetails);
11596                 if (first.pr != NoProc) {
11597                     SendToProgram(buf, &first);
11598                 }
11599                 if (second.pr != NoProc &&
11600                     gameMode == TwoMachinesPlay) {
11601                     SendToProgram(buf, &second);
11602                 }
11603             }
11604         }
11605
11606         if (appData.icsActive) {
11607             if (appData.quietPlay &&
11608                 (gameMode == IcsPlayingWhite ||
11609                  gameMode == IcsPlayingBlack)) {
11610                 SendToICS(ics_prefix);
11611                 SendToICS("set shout 1\n");
11612             }
11613             nextGameMode = IcsIdle;
11614             ics_user_moved = FALSE;
11615             /* clean up premove.  It's ugly when the game has ended and the
11616              * premove highlights are still on the board.
11617              */
11618             if (gotPremove) {
11619               gotPremove = FALSE;
11620               ClearPremoveHighlights();
11621               DrawPosition(FALSE, boards[currentMove]);
11622             }
11623             if (whosays == GE_ICS) {
11624                 switch (result) {
11625                 case WhiteWins:
11626                     if (gameMode == IcsPlayingWhite)
11627                         PlayIcsWinSound();
11628                     else if(gameMode == IcsPlayingBlack)
11629                         PlayIcsLossSound();
11630                     break;
11631                 case BlackWins:
11632                     if (gameMode == IcsPlayingBlack)
11633                         PlayIcsWinSound();
11634                     else if(gameMode == IcsPlayingWhite)
11635                         PlayIcsLossSound();
11636                     break;
11637                 case GameIsDrawn:
11638                     PlayIcsDrawSound();
11639                     break;
11640                 default:
11641                     PlayIcsUnfinishedSound();
11642                 }
11643             }
11644             if(appData.quitNext) { ExitEvent(0); return; }
11645         } else if (gameMode == EditGame ||
11646                    gameMode == PlayFromGameFile ||
11647                    gameMode == AnalyzeMode ||
11648                    gameMode == AnalyzeFile) {
11649             nextGameMode = gameMode;
11650         } else {
11651             nextGameMode = EndOfGame;
11652         }
11653         pausing = FALSE;
11654         ModeHighlight();
11655     } else {
11656         nextGameMode = gameMode;
11657     }
11658
11659     if (appData.noChessProgram) {
11660         gameMode = nextGameMode;
11661         ModeHighlight();
11662         endingGame = 0; /* [HGM] crash */
11663         return;
11664     }
11665
11666     if (first.reuse) {
11667         /* Put first chess program into idle state */
11668         if (first.pr != NoProc &&
11669             (gameMode == MachinePlaysWhite ||
11670              gameMode == MachinePlaysBlack ||
11671              gameMode == TwoMachinesPlay ||
11672              gameMode == IcsPlayingWhite ||
11673              gameMode == IcsPlayingBlack ||
11674              gameMode == BeginningOfGame)) {
11675             SendToProgram("force\n", &first);
11676             if (first.usePing) {
11677               char buf[MSG_SIZ];
11678               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11679               SendToProgram(buf, &first);
11680             }
11681         }
11682     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11683         /* Kill off first chess program */
11684         if (first.isr != NULL)
11685           RemoveInputSource(first.isr);
11686         first.isr = NULL;
11687
11688         if (first.pr != NoProc) {
11689             ExitAnalyzeMode();
11690             DoSleep( appData.delayBeforeQuit );
11691             SendToProgram("quit\n", &first);
11692             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11693             first.reload = TRUE;
11694         }
11695         first.pr = NoProc;
11696     }
11697     if (second.reuse) {
11698         /* Put second chess program into idle state */
11699         if (second.pr != NoProc &&
11700             gameMode == TwoMachinesPlay) {
11701             SendToProgram("force\n", &second);
11702             if (second.usePing) {
11703               char buf[MSG_SIZ];
11704               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11705               SendToProgram(buf, &second);
11706             }
11707         }
11708     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11709         /* Kill off second chess program */
11710         if (second.isr != NULL)
11711           RemoveInputSource(second.isr);
11712         second.isr = NULL;
11713
11714         if (second.pr != NoProc) {
11715             DoSleep( appData.delayBeforeQuit );
11716             SendToProgram("quit\n", &second);
11717             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11718             second.reload = TRUE;
11719         }
11720         second.pr = NoProc;
11721     }
11722
11723     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11724         char resChar = '=';
11725         switch (result) {
11726         case WhiteWins:
11727           resChar = '+';
11728           if (first.twoMachinesColor[0] == 'w') {
11729             first.matchWins++;
11730           } else {
11731             second.matchWins++;
11732           }
11733           break;
11734         case BlackWins:
11735           resChar = '-';
11736           if (first.twoMachinesColor[0] == 'b') {
11737             first.matchWins++;
11738           } else {
11739             second.matchWins++;
11740           }
11741           break;
11742         case GameUnfinished:
11743           resChar = ' ';
11744         default:
11745           break;
11746         }
11747
11748         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11749         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11750             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11751             ReserveGame(nextGame, resChar); // sets nextGame
11752             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11753             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11754         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11755
11756         if (nextGame <= appData.matchGames && !abortMatch) {
11757             gameMode = nextGameMode;
11758             matchGame = nextGame; // this will be overruled in tourney mode!
11759             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11760             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11761             endingGame = 0; /* [HGM] crash */
11762             return;
11763         } else {
11764             gameMode = nextGameMode;
11765             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11766                      first.tidy, second.tidy,
11767                      first.matchWins, second.matchWins,
11768                      appData.matchGames - (first.matchWins + second.matchWins));
11769             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11770             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11771             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11772             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11773                 first.twoMachinesColor = "black\n";
11774                 second.twoMachinesColor = "white\n";
11775             } else {
11776                 first.twoMachinesColor = "white\n";
11777                 second.twoMachinesColor = "black\n";
11778             }
11779         }
11780     }
11781     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11782         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11783       ExitAnalyzeMode();
11784     gameMode = nextGameMode;
11785     ModeHighlight();
11786     endingGame = 0;  /* [HGM] crash */
11787     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11788         if(matchMode == TRUE) { // match through command line: exit with or without popup
11789             if(ranking) {
11790                 ToNrEvent(forwardMostMove);
11791                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11792                 else ExitEvent(0);
11793             } else DisplayFatalError(buf, 0, 0);
11794         } else { // match through menu; just stop, with or without popup
11795             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11796             ModeHighlight();
11797             if(ranking){
11798                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11799             } else DisplayNote(buf);
11800       }
11801       if(ranking) free(ranking);
11802     }
11803 }
11804
11805 /* Assumes program was just initialized (initString sent).
11806    Leaves program in force mode. */
11807 void
11808 FeedMovesToProgram (ChessProgramState *cps, int upto)
11809 {
11810     int i;
11811
11812     if (appData.debugMode)
11813       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11814               startedFromSetupPosition ? "position and " : "",
11815               backwardMostMove, upto, cps->which);
11816     if(currentlyInitializedVariant != gameInfo.variant) {
11817       char buf[MSG_SIZ];
11818         // [HGM] variantswitch: make engine aware of new variant
11819         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11820                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11821                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11822         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11823         SendToProgram(buf, cps);
11824         currentlyInitializedVariant = gameInfo.variant;
11825     }
11826     SendToProgram("force\n", cps);
11827     if (startedFromSetupPosition) {
11828         SendBoard(cps, backwardMostMove);
11829     if (appData.debugMode) {
11830         fprintf(debugFP, "feedMoves\n");
11831     }
11832     }
11833     for (i = backwardMostMove; i < upto; i++) {
11834         SendMoveToProgram(i, cps);
11835     }
11836 }
11837
11838
11839 int
11840 ResurrectChessProgram ()
11841 {
11842      /* The chess program may have exited.
11843         If so, restart it and feed it all the moves made so far. */
11844     static int doInit = 0;
11845
11846     if (appData.noChessProgram) return 1;
11847
11848     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11849         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11850         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11851         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11852     } else {
11853         if (first.pr != NoProc) return 1;
11854         StartChessProgram(&first);
11855     }
11856     InitChessProgram(&first, FALSE);
11857     FeedMovesToProgram(&first, currentMove);
11858
11859     if (!first.sendTime) {
11860         /* can't tell gnuchess what its clock should read,
11861            so we bow to its notion. */
11862         ResetClocks();
11863         timeRemaining[0][currentMove] = whiteTimeRemaining;
11864         timeRemaining[1][currentMove] = blackTimeRemaining;
11865     }
11866
11867     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11868                 appData.icsEngineAnalyze) && first.analysisSupport) {
11869       SendToProgram("analyze\n", &first);
11870       first.analyzing = TRUE;
11871     }
11872     return 1;
11873 }
11874
11875 /*
11876  * Button procedures
11877  */
11878 void
11879 Reset (int redraw, int init)
11880 {
11881     int i;
11882
11883     if (appData.debugMode) {
11884         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11885                 redraw, init, gameMode);
11886     }
11887     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11888     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11889     CleanupTail(); // [HGM] vari: delete any stored variations
11890     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11891     pausing = pauseExamInvalid = FALSE;
11892     startedFromSetupPosition = blackPlaysFirst = FALSE;
11893     firstMove = TRUE;
11894     whiteFlag = blackFlag = FALSE;
11895     userOfferedDraw = FALSE;
11896     hintRequested = bookRequested = FALSE;
11897     first.maybeThinking = FALSE;
11898     second.maybeThinking = FALSE;
11899     first.bookSuspend = FALSE; // [HGM] book
11900     second.bookSuspend = FALSE;
11901     thinkOutput[0] = NULLCHAR;
11902     lastHint[0] = NULLCHAR;
11903     ClearGameInfo(&gameInfo);
11904     gameInfo.variant = StringToVariant(appData.variant);
11905     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11906     ics_user_moved = ics_clock_paused = FALSE;
11907     ics_getting_history = H_FALSE;
11908     ics_gamenum = -1;
11909     white_holding[0] = black_holding[0] = NULLCHAR;
11910     ClearProgramStats();
11911     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11912
11913     ResetFrontEnd();
11914     ClearHighlights();
11915     flipView = appData.flipView;
11916     ClearPremoveHighlights();
11917     gotPremove = FALSE;
11918     alarmSounded = FALSE;
11919     killX = killY = -1; // [HGM] lion
11920
11921     GameEnds(EndOfFile, NULL, GE_PLAYER);
11922     if(appData.serverMovesName != NULL) {
11923         /* [HGM] prepare to make moves file for broadcasting */
11924         clock_t t = clock();
11925         if(serverMoves != NULL) fclose(serverMoves);
11926         serverMoves = fopen(appData.serverMovesName, "r");
11927         if(serverMoves != NULL) {
11928             fclose(serverMoves);
11929             /* delay 15 sec before overwriting, so all clients can see end */
11930             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11931         }
11932         serverMoves = fopen(appData.serverMovesName, "w");
11933     }
11934
11935     ExitAnalyzeMode();
11936     gameMode = BeginningOfGame;
11937     ModeHighlight();
11938     if(appData.icsActive) gameInfo.variant = VariantNormal;
11939     currentMove = forwardMostMove = backwardMostMove = 0;
11940     MarkTargetSquares(1);
11941     InitPosition(redraw);
11942     for (i = 0; i < MAX_MOVES; i++) {
11943         if (commentList[i] != NULL) {
11944             free(commentList[i]);
11945             commentList[i] = NULL;
11946         }
11947     }
11948     ResetClocks();
11949     timeRemaining[0][0] = whiteTimeRemaining;
11950     timeRemaining[1][0] = blackTimeRemaining;
11951
11952     if (first.pr == NoProc) {
11953         StartChessProgram(&first);
11954     }
11955     if (init) {
11956             InitChessProgram(&first, startedFromSetupPosition);
11957     }
11958     DisplayTitle("");
11959     DisplayMessage("", "");
11960     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11961     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11962     ClearMap();        // [HGM] exclude: invalidate map
11963 }
11964
11965 void
11966 AutoPlayGameLoop ()
11967 {
11968     for (;;) {
11969         if (!AutoPlayOneMove())
11970           return;
11971         if (matchMode || appData.timeDelay == 0)
11972           continue;
11973         if (appData.timeDelay < 0)
11974           return;
11975         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11976         break;
11977     }
11978 }
11979
11980 void
11981 AnalyzeNextGame()
11982 {
11983     ReloadGame(1); // next game
11984 }
11985
11986 int
11987 AutoPlayOneMove ()
11988 {
11989     int fromX, fromY, toX, toY;
11990
11991     if (appData.debugMode) {
11992       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11993     }
11994
11995     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11996       return FALSE;
11997
11998     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11999       pvInfoList[currentMove].depth = programStats.depth;
12000       pvInfoList[currentMove].score = programStats.score;
12001       pvInfoList[currentMove].time  = 0;
12002       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12003       else { // append analysis of final position as comment
12004         char buf[MSG_SIZ];
12005         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12006         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12007       }
12008       programStats.depth = 0;
12009     }
12010
12011     if (currentMove >= forwardMostMove) {
12012       if(gameMode == AnalyzeFile) {
12013           if(appData.loadGameIndex == -1) {
12014             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12015           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12016           } else {
12017           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12018         }
12019       }
12020 //      gameMode = EndOfGame;
12021 //      ModeHighlight();
12022
12023       /* [AS] Clear current move marker at the end of a game */
12024       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12025
12026       return FALSE;
12027     }
12028
12029     toX = moveList[currentMove][2] - AAA;
12030     toY = moveList[currentMove][3] - ONE;
12031
12032     if (moveList[currentMove][1] == '@') {
12033         if (appData.highlightLastMove) {
12034             SetHighlights(-1, -1, toX, toY);
12035         }
12036     } else {
12037         int viaX = moveList[currentMove][5] - AAA;
12038         int viaY = moveList[currentMove][6] - ONE;
12039         fromX = moveList[currentMove][0] - AAA;
12040         fromY = moveList[currentMove][1] - ONE;
12041
12042         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12043
12044         if(moveList[currentMove][4] == ';') { // multi-leg
12045             ChessSquare piece = boards[currentMove][viaY][viaX];
12046             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
12047             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
12048             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
12049             boards[currentMove][viaY][viaX] = piece;
12050         } else
12051         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12052
12053         if (appData.highlightLastMove) {
12054             SetHighlights(fromX, fromY, toX, toY);
12055         }
12056     }
12057     DisplayMove(currentMove);
12058     SendMoveToProgram(currentMove++, &first);
12059     DisplayBothClocks();
12060     DrawPosition(FALSE, boards[currentMove]);
12061     // [HGM] PV info: always display, routine tests if empty
12062     DisplayComment(currentMove - 1, commentList[currentMove]);
12063     return TRUE;
12064 }
12065
12066
12067 int
12068 LoadGameOneMove (ChessMove readAhead)
12069 {
12070     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12071     char promoChar = NULLCHAR;
12072     ChessMove moveType;
12073     char move[MSG_SIZ];
12074     char *p, *q;
12075
12076     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12077         gameMode != AnalyzeMode && gameMode != Training) {
12078         gameFileFP = NULL;
12079         return FALSE;
12080     }
12081
12082     yyboardindex = forwardMostMove;
12083     if (readAhead != EndOfFile) {
12084       moveType = readAhead;
12085     } else {
12086       if (gameFileFP == NULL)
12087           return FALSE;
12088       moveType = (ChessMove) Myylex();
12089     }
12090
12091     done = FALSE;
12092     switch (moveType) {
12093       case Comment:
12094         if (appData.debugMode)
12095           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12096         p = yy_text;
12097
12098         /* append the comment but don't display it */
12099         AppendComment(currentMove, p, FALSE);
12100         return TRUE;
12101
12102       case WhiteCapturesEnPassant:
12103       case BlackCapturesEnPassant:
12104       case WhitePromotion:
12105       case BlackPromotion:
12106       case WhiteNonPromotion:
12107       case BlackNonPromotion:
12108       case NormalMove:
12109       case FirstLeg:
12110       case WhiteKingSideCastle:
12111       case WhiteQueenSideCastle:
12112       case BlackKingSideCastle:
12113       case BlackQueenSideCastle:
12114       case WhiteKingSideCastleWild:
12115       case WhiteQueenSideCastleWild:
12116       case BlackKingSideCastleWild:
12117       case BlackQueenSideCastleWild:
12118       /* PUSH Fabien */
12119       case WhiteHSideCastleFR:
12120       case WhiteASideCastleFR:
12121       case BlackHSideCastleFR:
12122       case BlackASideCastleFR:
12123       /* POP Fabien */
12124         if (appData.debugMode)
12125           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12126         fromX = currentMoveString[0] - AAA;
12127         fromY = currentMoveString[1] - ONE;
12128         toX = currentMoveString[2] - AAA;
12129         toY = currentMoveString[3] - ONE;
12130         promoChar = currentMoveString[4];
12131         if(promoChar == ';') promoChar = NULLCHAR;
12132         break;
12133
12134       case WhiteDrop:
12135       case BlackDrop:
12136         if (appData.debugMode)
12137           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12138         fromX = moveType == WhiteDrop ?
12139           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12140         (int) CharToPiece(ToLower(currentMoveString[0]));
12141         fromY = DROP_RANK;
12142         toX = currentMoveString[2] - AAA;
12143         toY = currentMoveString[3] - ONE;
12144         break;
12145
12146       case WhiteWins:
12147       case BlackWins:
12148       case GameIsDrawn:
12149       case GameUnfinished:
12150         if (appData.debugMode)
12151           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12152         p = strchr(yy_text, '{');
12153         if (p == NULL) p = strchr(yy_text, '(');
12154         if (p == NULL) {
12155             p = yy_text;
12156             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12157         } else {
12158             q = strchr(p, *p == '{' ? '}' : ')');
12159             if (q != NULL) *q = NULLCHAR;
12160             p++;
12161         }
12162         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12163         GameEnds(moveType, p, GE_FILE);
12164         done = TRUE;
12165         if (cmailMsgLoaded) {
12166             ClearHighlights();
12167             flipView = WhiteOnMove(currentMove);
12168             if (moveType == GameUnfinished) flipView = !flipView;
12169             if (appData.debugMode)
12170               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12171         }
12172         break;
12173
12174       case EndOfFile:
12175         if (appData.debugMode)
12176           fprintf(debugFP, "Parser hit end of file\n");
12177         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12178           case MT_NONE:
12179           case MT_CHECK:
12180             break;
12181           case MT_CHECKMATE:
12182           case MT_STAINMATE:
12183             if (WhiteOnMove(currentMove)) {
12184                 GameEnds(BlackWins, "Black mates", GE_FILE);
12185             } else {
12186                 GameEnds(WhiteWins, "White mates", GE_FILE);
12187             }
12188             break;
12189           case MT_STALEMATE:
12190             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12191             break;
12192         }
12193         done = TRUE;
12194         break;
12195
12196       case MoveNumberOne:
12197         if (lastLoadGameStart == GNUChessGame) {
12198             /* GNUChessGames have numbers, but they aren't move numbers */
12199             if (appData.debugMode)
12200               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12201                       yy_text, (int) moveType);
12202             return LoadGameOneMove(EndOfFile); /* tail recursion */
12203         }
12204         /* else fall thru */
12205
12206       case XBoardGame:
12207       case GNUChessGame:
12208       case PGNTag:
12209         /* Reached start of next game in file */
12210         if (appData.debugMode)
12211           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12212         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12213           case MT_NONE:
12214           case MT_CHECK:
12215             break;
12216           case MT_CHECKMATE:
12217           case MT_STAINMATE:
12218             if (WhiteOnMove(currentMove)) {
12219                 GameEnds(BlackWins, "Black mates", GE_FILE);
12220             } else {
12221                 GameEnds(WhiteWins, "White mates", GE_FILE);
12222             }
12223             break;
12224           case MT_STALEMATE:
12225             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12226             break;
12227         }
12228         done = TRUE;
12229         break;
12230
12231       case PositionDiagram:     /* should not happen; ignore */
12232       case ElapsedTime:         /* ignore */
12233       case NAG:                 /* ignore */
12234         if (appData.debugMode)
12235           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12236                   yy_text, (int) moveType);
12237         return LoadGameOneMove(EndOfFile); /* tail recursion */
12238
12239       case IllegalMove:
12240         if (appData.testLegality) {
12241             if (appData.debugMode)
12242               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12243             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12244                     (forwardMostMove / 2) + 1,
12245                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12246             DisplayError(move, 0);
12247             done = TRUE;
12248         } else {
12249             if (appData.debugMode)
12250               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12251                       yy_text, currentMoveString);
12252             if(currentMoveString[1] == '@') {
12253                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12254                 fromY = DROP_RANK;
12255             } else {
12256                 fromX = currentMoveString[0] - AAA;
12257                 fromY = currentMoveString[1] - ONE;
12258             }
12259             toX = currentMoveString[2] - AAA;
12260             toY = currentMoveString[3] - ONE;
12261             promoChar = currentMoveString[4];
12262         }
12263         break;
12264
12265       case AmbiguousMove:
12266         if (appData.debugMode)
12267           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12268         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12269                 (forwardMostMove / 2) + 1,
12270                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12271         DisplayError(move, 0);
12272         done = TRUE;
12273         break;
12274
12275       default:
12276       case ImpossibleMove:
12277         if (appData.debugMode)
12278           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12279         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12280                 (forwardMostMove / 2) + 1,
12281                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12282         DisplayError(move, 0);
12283         done = TRUE;
12284         break;
12285     }
12286
12287     if (done) {
12288         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12289             DrawPosition(FALSE, boards[currentMove]);
12290             DisplayBothClocks();
12291             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12292               DisplayComment(currentMove - 1, commentList[currentMove]);
12293         }
12294         (void) StopLoadGameTimer();
12295         gameFileFP = NULL;
12296         cmailOldMove = forwardMostMove;
12297         return FALSE;
12298     } else {
12299         /* currentMoveString is set as a side-effect of yylex */
12300
12301         thinkOutput[0] = NULLCHAR;
12302         MakeMove(fromX, fromY, toX, toY, promoChar);
12303         killX = killY = -1; // [HGM] lion: used up
12304         currentMove = forwardMostMove;
12305         return TRUE;
12306     }
12307 }
12308
12309 /* Load the nth game from the given file */
12310 int
12311 LoadGameFromFile (char *filename, int n, char *title, int useList)
12312 {
12313     FILE *f;
12314     char buf[MSG_SIZ];
12315
12316     if (strcmp(filename, "-") == 0) {
12317         f = stdin;
12318         title = "stdin";
12319     } else {
12320         f = fopen(filename, "rb");
12321         if (f == NULL) {
12322           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12323             DisplayError(buf, errno);
12324             return FALSE;
12325         }
12326     }
12327     if (fseek(f, 0, 0) == -1) {
12328         /* f is not seekable; probably a pipe */
12329         useList = FALSE;
12330     }
12331     if (useList && n == 0) {
12332         int error = GameListBuild(f);
12333         if (error) {
12334             DisplayError(_("Cannot build game list"), error);
12335         } else if (!ListEmpty(&gameList) &&
12336                    ((ListGame *) gameList.tailPred)->number > 1) {
12337             GameListPopUp(f, title);
12338             return TRUE;
12339         }
12340         GameListDestroy();
12341         n = 1;
12342     }
12343     if (n == 0) n = 1;
12344     return LoadGame(f, n, title, FALSE);
12345 }
12346
12347
12348 void
12349 MakeRegisteredMove ()
12350 {
12351     int fromX, fromY, toX, toY;
12352     char promoChar;
12353     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12354         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12355           case CMAIL_MOVE:
12356           case CMAIL_DRAW:
12357             if (appData.debugMode)
12358               fprintf(debugFP, "Restoring %s for game %d\n",
12359                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12360
12361             thinkOutput[0] = NULLCHAR;
12362             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12363             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12364             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12365             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12366             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12367             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12368             MakeMove(fromX, fromY, toX, toY, promoChar);
12369             ShowMove(fromX, fromY, toX, toY);
12370
12371             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12372               case MT_NONE:
12373               case MT_CHECK:
12374                 break;
12375
12376               case MT_CHECKMATE:
12377               case MT_STAINMATE:
12378                 if (WhiteOnMove(currentMove)) {
12379                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12380                 } else {
12381                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12382                 }
12383                 break;
12384
12385               case MT_STALEMATE:
12386                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12387                 break;
12388             }
12389
12390             break;
12391
12392           case CMAIL_RESIGN:
12393             if (WhiteOnMove(currentMove)) {
12394                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12395             } else {
12396                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12397             }
12398             break;
12399
12400           case CMAIL_ACCEPT:
12401             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12402             break;
12403
12404           default:
12405             break;
12406         }
12407     }
12408
12409     return;
12410 }
12411
12412 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12413 int
12414 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12415 {
12416     int retVal;
12417
12418     if (gameNumber > nCmailGames) {
12419         DisplayError(_("No more games in this message"), 0);
12420         return FALSE;
12421     }
12422     if (f == lastLoadGameFP) {
12423         int offset = gameNumber - lastLoadGameNumber;
12424         if (offset == 0) {
12425             cmailMsg[0] = NULLCHAR;
12426             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12427                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12428                 nCmailMovesRegistered--;
12429             }
12430             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12431             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12432                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12433             }
12434         } else {
12435             if (! RegisterMove()) return FALSE;
12436         }
12437     }
12438
12439     retVal = LoadGame(f, gameNumber, title, useList);
12440
12441     /* Make move registered during previous look at this game, if any */
12442     MakeRegisteredMove();
12443
12444     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12445         commentList[currentMove]
12446           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12447         DisplayComment(currentMove - 1, commentList[currentMove]);
12448     }
12449
12450     return retVal;
12451 }
12452
12453 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12454 int
12455 ReloadGame (int offset)
12456 {
12457     int gameNumber = lastLoadGameNumber + offset;
12458     if (lastLoadGameFP == NULL) {
12459         DisplayError(_("No game has been loaded yet"), 0);
12460         return FALSE;
12461     }
12462     if (gameNumber <= 0) {
12463         DisplayError(_("Can't back up any further"), 0);
12464         return FALSE;
12465     }
12466     if (cmailMsgLoaded) {
12467         return CmailLoadGame(lastLoadGameFP, gameNumber,
12468                              lastLoadGameTitle, lastLoadGameUseList);
12469     } else {
12470         return LoadGame(lastLoadGameFP, gameNumber,
12471                         lastLoadGameTitle, lastLoadGameUseList);
12472     }
12473 }
12474
12475 int keys[EmptySquare+1];
12476
12477 int
12478 PositionMatches (Board b1, Board b2)
12479 {
12480     int r, f, sum=0;
12481     switch(appData.searchMode) {
12482         case 1: return CompareWithRights(b1, b2);
12483         case 2:
12484             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12485                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12486             }
12487             return TRUE;
12488         case 3:
12489             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12490               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12491                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12492             }
12493             return sum==0;
12494         case 4:
12495             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12496                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12497             }
12498             return sum==0;
12499     }
12500     return TRUE;
12501 }
12502
12503 #define Q_PROMO  4
12504 #define Q_EP     3
12505 #define Q_BCASTL 2
12506 #define Q_WCASTL 1
12507
12508 int pieceList[256], quickBoard[256];
12509 ChessSquare pieceType[256] = { EmptySquare };
12510 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12511 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12512 int soughtTotal, turn;
12513 Boolean epOK, flipSearch;
12514
12515 typedef struct {
12516     unsigned char piece, to;
12517 } Move;
12518
12519 #define DSIZE (250000)
12520
12521 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12522 Move *moveDatabase = initialSpace;
12523 unsigned int movePtr, dataSize = DSIZE;
12524
12525 int
12526 MakePieceList (Board board, int *counts)
12527 {
12528     int r, f, n=Q_PROMO, total=0;
12529     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12530     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12531         int sq = f + (r<<4);
12532         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12533             quickBoard[sq] = ++n;
12534             pieceList[n] = sq;
12535             pieceType[n] = board[r][f];
12536             counts[board[r][f]]++;
12537             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12538             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12539             total++;
12540         }
12541     }
12542     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12543     return total;
12544 }
12545
12546 void
12547 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12548 {
12549     int sq = fromX + (fromY<<4);
12550     int piece = quickBoard[sq], rook;
12551     quickBoard[sq] = 0;
12552     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12553     if(piece == pieceList[1] && fromY == toY) {
12554       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12555         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12556         moveDatabase[movePtr++].piece = Q_WCASTL;
12557         quickBoard[sq] = piece;
12558         piece = quickBoard[from]; quickBoard[from] = 0;
12559         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12560       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12561         quickBoard[sq] = 0; // remove Rook
12562         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12563         moveDatabase[movePtr++].piece = Q_WCASTL;
12564         quickBoard[sq] = pieceList[1]; // put King
12565         piece = rook;
12566         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12567       }
12568     } else
12569     if(piece == pieceList[2] && fromY == toY) {
12570       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12571         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12572         moveDatabase[movePtr++].piece = Q_BCASTL;
12573         quickBoard[sq] = piece;
12574         piece = quickBoard[from]; quickBoard[from] = 0;
12575         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12576       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12577         quickBoard[sq] = 0; // remove Rook
12578         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12579         moveDatabase[movePtr++].piece = Q_BCASTL;
12580         quickBoard[sq] = pieceList[2]; // put King
12581         piece = rook;
12582         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12583       }
12584     } else
12585     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12586         quickBoard[(fromY<<4)+toX] = 0;
12587         moveDatabase[movePtr].piece = Q_EP;
12588         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12589         moveDatabase[movePtr].to = sq;
12590     } else
12591     if(promoPiece != pieceType[piece]) {
12592         moveDatabase[movePtr++].piece = Q_PROMO;
12593         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12594     }
12595     moveDatabase[movePtr].piece = piece;
12596     quickBoard[sq] = piece;
12597     movePtr++;
12598 }
12599
12600 int
12601 PackGame (Board board)
12602 {
12603     Move *newSpace = NULL;
12604     moveDatabase[movePtr].piece = 0; // terminate previous game
12605     if(movePtr > dataSize) {
12606         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12607         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12608         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12609         if(newSpace) {
12610             int i;
12611             Move *p = moveDatabase, *q = newSpace;
12612             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12613             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12614             moveDatabase = newSpace;
12615         } else { // calloc failed, we must be out of memory. Too bad...
12616             dataSize = 0; // prevent calloc events for all subsequent games
12617             return 0;     // and signal this one isn't cached
12618         }
12619     }
12620     movePtr++;
12621     MakePieceList(board, counts);
12622     return movePtr;
12623 }
12624
12625 int
12626 QuickCompare (Board board, int *minCounts, int *maxCounts)
12627 {   // compare according to search mode
12628     int r, f;
12629     switch(appData.searchMode)
12630     {
12631       case 1: // exact position match
12632         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12633         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12634             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12635         }
12636         break;
12637       case 2: // can have extra material on empty squares
12638         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12639             if(board[r][f] == EmptySquare) continue;
12640             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12641         }
12642         break;
12643       case 3: // material with exact Pawn structure
12644         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12645             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12646             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12647         } // fall through to material comparison
12648       case 4: // exact material
12649         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12650         break;
12651       case 6: // material range with given imbalance
12652         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12653         // fall through to range comparison
12654       case 5: // material range
12655         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12656     }
12657     return TRUE;
12658 }
12659
12660 int
12661 QuickScan (Board board, Move *move)
12662 {   // reconstruct game,and compare all positions in it
12663     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12664     do {
12665         int piece = move->piece;
12666         int to = move->to, from = pieceList[piece];
12667         if(found < 0) { // if already found just scan to game end for final piece count
12668           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12669            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12670            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12671                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12672             ) {
12673             static int lastCounts[EmptySquare+1];
12674             int i;
12675             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12676             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12677           } else stretch = 0;
12678           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12679           if(found >= 0 && !appData.minPieces) return found;
12680         }
12681         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12682           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12683           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12684             piece = (++move)->piece;
12685             from = pieceList[piece];
12686             counts[pieceType[piece]]--;
12687             pieceType[piece] = (ChessSquare) move->to;
12688             counts[move->to]++;
12689           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12690             counts[pieceType[quickBoard[to]]]--;
12691             quickBoard[to] = 0; total--;
12692             move++;
12693             continue;
12694           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12695             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12696             from  = pieceList[piece]; // so this must be King
12697             quickBoard[from] = 0;
12698             pieceList[piece] = to;
12699             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12700             quickBoard[from] = 0; // rook
12701             quickBoard[to] = piece;
12702             to = move->to; piece = move->piece;
12703             goto aftercastle;
12704           }
12705         }
12706         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12707         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12708         quickBoard[from] = 0;
12709       aftercastle:
12710         quickBoard[to] = piece;
12711         pieceList[piece] = to;
12712         cnt++; turn ^= 3;
12713         move++;
12714     } while(1);
12715 }
12716
12717 void
12718 InitSearch ()
12719 {
12720     int r, f;
12721     flipSearch = FALSE;
12722     CopyBoard(soughtBoard, boards[currentMove]);
12723     soughtTotal = MakePieceList(soughtBoard, maxSought);
12724     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12725     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12726     CopyBoard(reverseBoard, boards[currentMove]);
12727     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12728         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12729         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12730         reverseBoard[r][f] = piece;
12731     }
12732     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12733     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12734     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12735                  || (boards[currentMove][CASTLING][2] == NoRights ||
12736                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12737                  && (boards[currentMove][CASTLING][5] == NoRights ||
12738                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12739       ) {
12740         flipSearch = TRUE;
12741         CopyBoard(flipBoard, soughtBoard);
12742         CopyBoard(rotateBoard, reverseBoard);
12743         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12744             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12745             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12746         }
12747     }
12748     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12749     if(appData.searchMode >= 5) {
12750         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12751         MakePieceList(soughtBoard, minSought);
12752         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12753     }
12754     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12755         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12756 }
12757
12758 GameInfo dummyInfo;
12759 static int creatingBook;
12760
12761 int
12762 GameContainsPosition (FILE *f, ListGame *lg)
12763 {
12764     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12765     int fromX, fromY, toX, toY;
12766     char promoChar;
12767     static int initDone=FALSE;
12768
12769     // weed out games based on numerical tag comparison
12770     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12771     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12772     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12773     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12774     if(!initDone) {
12775         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12776         initDone = TRUE;
12777     }
12778     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12779     else CopyBoard(boards[scratch], initialPosition); // default start position
12780     if(lg->moves) {
12781         turn = btm + 1;
12782         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12783         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12784     }
12785     if(btm) plyNr++;
12786     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12787     fseek(f, lg->offset, 0);
12788     yynewfile(f);
12789     while(1) {
12790         yyboardindex = scratch;
12791         quickFlag = plyNr+1;
12792         next = Myylex();
12793         quickFlag = 0;
12794         switch(next) {
12795             case PGNTag:
12796                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12797             default:
12798                 continue;
12799
12800             case XBoardGame:
12801             case GNUChessGame:
12802                 if(plyNr) return -1; // after we have seen moves, this is for new game
12803               continue;
12804
12805             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12806             case ImpossibleMove:
12807             case WhiteWins: // game ends here with these four
12808             case BlackWins:
12809             case GameIsDrawn:
12810             case GameUnfinished:
12811                 return -1;
12812
12813             case IllegalMove:
12814                 if(appData.testLegality) return -1;
12815             case WhiteCapturesEnPassant:
12816             case BlackCapturesEnPassant:
12817             case WhitePromotion:
12818             case BlackPromotion:
12819             case WhiteNonPromotion:
12820             case BlackNonPromotion:
12821             case NormalMove:
12822             case FirstLeg:
12823             case WhiteKingSideCastle:
12824             case WhiteQueenSideCastle:
12825             case BlackKingSideCastle:
12826             case BlackQueenSideCastle:
12827             case WhiteKingSideCastleWild:
12828             case WhiteQueenSideCastleWild:
12829             case BlackKingSideCastleWild:
12830             case BlackQueenSideCastleWild:
12831             case WhiteHSideCastleFR:
12832             case WhiteASideCastleFR:
12833             case BlackHSideCastleFR:
12834             case BlackASideCastleFR:
12835                 fromX = currentMoveString[0] - AAA;
12836                 fromY = currentMoveString[1] - ONE;
12837                 toX = currentMoveString[2] - AAA;
12838                 toY = currentMoveString[3] - ONE;
12839                 promoChar = currentMoveString[4];
12840                 break;
12841             case WhiteDrop:
12842             case BlackDrop:
12843                 fromX = next == WhiteDrop ?
12844                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12845                   (int) CharToPiece(ToLower(currentMoveString[0]));
12846                 fromY = DROP_RANK;
12847                 toX = currentMoveString[2] - AAA;
12848                 toY = currentMoveString[3] - ONE;
12849                 promoChar = 0;
12850                 break;
12851         }
12852         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12853         plyNr++;
12854         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12855         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12856         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12857         if(appData.findMirror) {
12858             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12859             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12860         }
12861     }
12862 }
12863
12864 /* Load the nth game from open file f */
12865 int
12866 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12867 {
12868     ChessMove cm;
12869     char buf[MSG_SIZ];
12870     int gn = gameNumber;
12871     ListGame *lg = NULL;
12872     int numPGNTags = 0;
12873     int err, pos = -1;
12874     GameMode oldGameMode;
12875     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12876     char oldName[MSG_SIZ];
12877
12878     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12879
12880     if (appData.debugMode)
12881         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12882
12883     if (gameMode == Training )
12884         SetTrainingModeOff();
12885
12886     oldGameMode = gameMode;
12887     if (gameMode != BeginningOfGame) {
12888       Reset(FALSE, TRUE);
12889     }
12890     killX = killY = -1; // [HGM] lion: in case we did not Reset
12891
12892     gameFileFP = f;
12893     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12894         fclose(lastLoadGameFP);
12895     }
12896
12897     if (useList) {
12898         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12899
12900         if (lg) {
12901             fseek(f, lg->offset, 0);
12902             GameListHighlight(gameNumber);
12903             pos = lg->position;
12904             gn = 1;
12905         }
12906         else {
12907             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12908               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12909             else
12910             DisplayError(_("Game number out of range"), 0);
12911             return FALSE;
12912         }
12913     } else {
12914         GameListDestroy();
12915         if (fseek(f, 0, 0) == -1) {
12916             if (f == lastLoadGameFP ?
12917                 gameNumber == lastLoadGameNumber + 1 :
12918                 gameNumber == 1) {
12919                 gn = 1;
12920             } else {
12921                 DisplayError(_("Can't seek on game file"), 0);
12922                 return FALSE;
12923             }
12924         }
12925     }
12926     lastLoadGameFP = f;
12927     lastLoadGameNumber = gameNumber;
12928     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12929     lastLoadGameUseList = useList;
12930
12931     yynewfile(f);
12932
12933     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12934       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12935                 lg->gameInfo.black);
12936             DisplayTitle(buf);
12937     } else if (*title != NULLCHAR) {
12938         if (gameNumber > 1) {
12939           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12940             DisplayTitle(buf);
12941         } else {
12942             DisplayTitle(title);
12943         }
12944     }
12945
12946     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12947         gameMode = PlayFromGameFile;
12948         ModeHighlight();
12949     }
12950
12951     currentMove = forwardMostMove = backwardMostMove = 0;
12952     CopyBoard(boards[0], initialPosition);
12953     StopClocks();
12954
12955     /*
12956      * Skip the first gn-1 games in the file.
12957      * Also skip over anything that precedes an identifiable
12958      * start of game marker, to avoid being confused by
12959      * garbage at the start of the file.  Currently
12960      * recognized start of game markers are the move number "1",
12961      * the pattern "gnuchess .* game", the pattern
12962      * "^[#;%] [^ ]* game file", and a PGN tag block.
12963      * A game that starts with one of the latter two patterns
12964      * will also have a move number 1, possibly
12965      * following a position diagram.
12966      * 5-4-02: Let's try being more lenient and allowing a game to
12967      * start with an unnumbered move.  Does that break anything?
12968      */
12969     cm = lastLoadGameStart = EndOfFile;
12970     while (gn > 0) {
12971         yyboardindex = forwardMostMove;
12972         cm = (ChessMove) Myylex();
12973         switch (cm) {
12974           case EndOfFile:
12975             if (cmailMsgLoaded) {
12976                 nCmailGames = CMAIL_MAX_GAMES - gn;
12977             } else {
12978                 Reset(TRUE, TRUE);
12979                 DisplayError(_("Game not found in file"), 0);
12980             }
12981             return FALSE;
12982
12983           case GNUChessGame:
12984           case XBoardGame:
12985             gn--;
12986             lastLoadGameStart = cm;
12987             break;
12988
12989           case MoveNumberOne:
12990             switch (lastLoadGameStart) {
12991               case GNUChessGame:
12992               case XBoardGame:
12993               case PGNTag:
12994                 break;
12995               case MoveNumberOne:
12996               case EndOfFile:
12997                 gn--;           /* count this game */
12998                 lastLoadGameStart = cm;
12999                 break;
13000               default:
13001                 /* impossible */
13002                 break;
13003             }
13004             break;
13005
13006           case PGNTag:
13007             switch (lastLoadGameStart) {
13008               case GNUChessGame:
13009               case PGNTag:
13010               case MoveNumberOne:
13011               case EndOfFile:
13012                 gn--;           /* count this game */
13013                 lastLoadGameStart = cm;
13014                 break;
13015               case XBoardGame:
13016                 lastLoadGameStart = cm; /* game counted already */
13017                 break;
13018               default:
13019                 /* impossible */
13020                 break;
13021             }
13022             if (gn > 0) {
13023                 do {
13024                     yyboardindex = forwardMostMove;
13025                     cm = (ChessMove) Myylex();
13026                 } while (cm == PGNTag || cm == Comment);
13027             }
13028             break;
13029
13030           case WhiteWins:
13031           case BlackWins:
13032           case GameIsDrawn:
13033             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13034                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13035                     != CMAIL_OLD_RESULT) {
13036                     nCmailResults ++ ;
13037                     cmailResult[  CMAIL_MAX_GAMES
13038                                 - gn - 1] = CMAIL_OLD_RESULT;
13039                 }
13040             }
13041             break;
13042
13043           case NormalMove:
13044           case FirstLeg:
13045             /* Only a NormalMove can be at the start of a game
13046              * without a position diagram. */
13047             if (lastLoadGameStart == EndOfFile ) {
13048               gn--;
13049               lastLoadGameStart = MoveNumberOne;
13050             }
13051             break;
13052
13053           default:
13054             break;
13055         }
13056     }
13057
13058     if (appData.debugMode)
13059       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13060
13061     if (cm == XBoardGame) {
13062         /* Skip any header junk before position diagram and/or move 1 */
13063         for (;;) {
13064             yyboardindex = forwardMostMove;
13065             cm = (ChessMove) Myylex();
13066
13067             if (cm == EndOfFile ||
13068                 cm == GNUChessGame || cm == XBoardGame) {
13069                 /* Empty game; pretend end-of-file and handle later */
13070                 cm = EndOfFile;
13071                 break;
13072             }
13073
13074             if (cm == MoveNumberOne || cm == PositionDiagram ||
13075                 cm == PGNTag || cm == Comment)
13076               break;
13077         }
13078     } else if (cm == GNUChessGame) {
13079         if (gameInfo.event != NULL) {
13080             free(gameInfo.event);
13081         }
13082         gameInfo.event = StrSave(yy_text);
13083     }
13084
13085     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13086     while (cm == PGNTag) {
13087         if (appData.debugMode)
13088           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13089         err = ParsePGNTag(yy_text, &gameInfo);
13090         if (!err) numPGNTags++;
13091
13092         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13093         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13094             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13095             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13096             InitPosition(TRUE);
13097             oldVariant = gameInfo.variant;
13098             if (appData.debugMode)
13099               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13100         }
13101
13102
13103         if (gameInfo.fen != NULL) {
13104           Board initial_position;
13105           startedFromSetupPosition = TRUE;
13106           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13107             Reset(TRUE, TRUE);
13108             DisplayError(_("Bad FEN position in file"), 0);
13109             return FALSE;
13110           }
13111           CopyBoard(boards[0], initial_position);
13112           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13113             CopyBoard(initialPosition, initial_position);
13114           if (blackPlaysFirst) {
13115             currentMove = forwardMostMove = backwardMostMove = 1;
13116             CopyBoard(boards[1], initial_position);
13117             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13118             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13119             timeRemaining[0][1] = whiteTimeRemaining;
13120             timeRemaining[1][1] = blackTimeRemaining;
13121             if (commentList[0] != NULL) {
13122               commentList[1] = commentList[0];
13123               commentList[0] = NULL;
13124             }
13125           } else {
13126             currentMove = forwardMostMove = backwardMostMove = 0;
13127           }
13128           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13129           {   int i;
13130               initialRulePlies = FENrulePlies;
13131               for( i=0; i< nrCastlingRights; i++ )
13132                   initialRights[i] = initial_position[CASTLING][i];
13133           }
13134           yyboardindex = forwardMostMove;
13135           free(gameInfo.fen);
13136           gameInfo.fen = NULL;
13137         }
13138
13139         yyboardindex = forwardMostMove;
13140         cm = (ChessMove) Myylex();
13141
13142         /* Handle comments interspersed among the tags */
13143         while (cm == Comment) {
13144             char *p;
13145             if (appData.debugMode)
13146               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13147             p = yy_text;
13148             AppendComment(currentMove, p, FALSE);
13149             yyboardindex = forwardMostMove;
13150             cm = (ChessMove) Myylex();
13151         }
13152     }
13153
13154     /* don't rely on existence of Event tag since if game was
13155      * pasted from clipboard the Event tag may not exist
13156      */
13157     if (numPGNTags > 0){
13158         char *tags;
13159         if (gameInfo.variant == VariantNormal) {
13160           VariantClass v = StringToVariant(gameInfo.event);
13161           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13162           if(v < VariantShogi) gameInfo.variant = v;
13163         }
13164         if (!matchMode) {
13165           if( appData.autoDisplayTags ) {
13166             tags = PGNTags(&gameInfo);
13167             TagsPopUp(tags, CmailMsg());
13168             free(tags);
13169           }
13170         }
13171     } else {
13172         /* Make something up, but don't display it now */
13173         SetGameInfo();
13174         TagsPopDown();
13175     }
13176
13177     if (cm == PositionDiagram) {
13178         int i, j;
13179         char *p;
13180         Board initial_position;
13181
13182         if (appData.debugMode)
13183           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13184
13185         if (!startedFromSetupPosition) {
13186             p = yy_text;
13187             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13188               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13189                 switch (*p) {
13190                   case '{':
13191                   case '[':
13192                   case '-':
13193                   case ' ':
13194                   case '\t':
13195                   case '\n':
13196                   case '\r':
13197                     break;
13198                   default:
13199                     initial_position[i][j++] = CharToPiece(*p);
13200                     break;
13201                 }
13202             while (*p == ' ' || *p == '\t' ||
13203                    *p == '\n' || *p == '\r') p++;
13204
13205             if (strncmp(p, "black", strlen("black"))==0)
13206               blackPlaysFirst = TRUE;
13207             else
13208               blackPlaysFirst = FALSE;
13209             startedFromSetupPosition = TRUE;
13210
13211             CopyBoard(boards[0], initial_position);
13212             if (blackPlaysFirst) {
13213                 currentMove = forwardMostMove = backwardMostMove = 1;
13214                 CopyBoard(boards[1], initial_position);
13215                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13216                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13217                 timeRemaining[0][1] = whiteTimeRemaining;
13218                 timeRemaining[1][1] = blackTimeRemaining;
13219                 if (commentList[0] != NULL) {
13220                     commentList[1] = commentList[0];
13221                     commentList[0] = NULL;
13222                 }
13223             } else {
13224                 currentMove = forwardMostMove = backwardMostMove = 0;
13225             }
13226         }
13227         yyboardindex = forwardMostMove;
13228         cm = (ChessMove) Myylex();
13229     }
13230
13231   if(!creatingBook) {
13232     if (first.pr == NoProc) {
13233         StartChessProgram(&first);
13234     }
13235     InitChessProgram(&first, FALSE);
13236     if(gameInfo.variant == VariantUnknown && *oldName) {
13237         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13238         gameInfo.variant = v;
13239     }
13240     SendToProgram("force\n", &first);
13241     if (startedFromSetupPosition) {
13242         SendBoard(&first, forwardMostMove);
13243     if (appData.debugMode) {
13244         fprintf(debugFP, "Load Game\n");
13245     }
13246         DisplayBothClocks();
13247     }
13248   }
13249
13250     /* [HGM] server: flag to write setup moves in broadcast file as one */
13251     loadFlag = appData.suppressLoadMoves;
13252
13253     while (cm == Comment) {
13254         char *p;
13255         if (appData.debugMode)
13256           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13257         p = yy_text;
13258         AppendComment(currentMove, p, FALSE);
13259         yyboardindex = forwardMostMove;
13260         cm = (ChessMove) Myylex();
13261     }
13262
13263     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13264         cm == WhiteWins || cm == BlackWins ||
13265         cm == GameIsDrawn || cm == GameUnfinished) {
13266         DisplayMessage("", _("No moves in game"));
13267         if (cmailMsgLoaded) {
13268             if (appData.debugMode)
13269               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13270             ClearHighlights();
13271             flipView = FALSE;
13272         }
13273         DrawPosition(FALSE, boards[currentMove]);
13274         DisplayBothClocks();
13275         gameMode = EditGame;
13276         ModeHighlight();
13277         gameFileFP = NULL;
13278         cmailOldMove = 0;
13279         return TRUE;
13280     }
13281
13282     // [HGM] PV info: routine tests if comment empty
13283     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13284         DisplayComment(currentMove - 1, commentList[currentMove]);
13285     }
13286     if (!matchMode && appData.timeDelay != 0)
13287       DrawPosition(FALSE, boards[currentMove]);
13288
13289     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13290       programStats.ok_to_send = 1;
13291     }
13292
13293     /* if the first token after the PGN tags is a move
13294      * and not move number 1, retrieve it from the parser
13295      */
13296     if (cm != MoveNumberOne)
13297         LoadGameOneMove(cm);
13298
13299     /* load the remaining moves from the file */
13300     while (LoadGameOneMove(EndOfFile)) {
13301       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13302       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13303     }
13304
13305     /* rewind to the start of the game */
13306     currentMove = backwardMostMove;
13307
13308     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13309
13310     if (oldGameMode == AnalyzeFile) {
13311       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13312       AnalyzeFileEvent();
13313     } else
13314     if (oldGameMode == AnalyzeMode) {
13315       AnalyzeFileEvent();
13316     }
13317
13318     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13319         long int w, b; // [HGM] adjourn: restore saved clock times
13320         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13321         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13322             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13323             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13324         }
13325     }
13326
13327     if(creatingBook) return TRUE;
13328     if (!matchMode && pos > 0) {
13329         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13330     } else
13331     if (matchMode || appData.timeDelay == 0) {
13332       ToEndEvent();
13333     } else if (appData.timeDelay > 0) {
13334       AutoPlayGameLoop();
13335     }
13336
13337     if (appData.debugMode)
13338         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13339
13340     loadFlag = 0; /* [HGM] true game starts */
13341     return TRUE;
13342 }
13343
13344 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13345 int
13346 ReloadPosition (int offset)
13347 {
13348     int positionNumber = lastLoadPositionNumber + offset;
13349     if (lastLoadPositionFP == NULL) {
13350         DisplayError(_("No position has been loaded yet"), 0);
13351         return FALSE;
13352     }
13353     if (positionNumber <= 0) {
13354         DisplayError(_("Can't back up any further"), 0);
13355         return FALSE;
13356     }
13357     return LoadPosition(lastLoadPositionFP, positionNumber,
13358                         lastLoadPositionTitle);
13359 }
13360
13361 /* Load the nth position from the given file */
13362 int
13363 LoadPositionFromFile (char *filename, int n, char *title)
13364 {
13365     FILE *f;
13366     char buf[MSG_SIZ];
13367
13368     if (strcmp(filename, "-") == 0) {
13369         return LoadPosition(stdin, n, "stdin");
13370     } else {
13371         f = fopen(filename, "rb");
13372         if (f == NULL) {
13373             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13374             DisplayError(buf, errno);
13375             return FALSE;
13376         } else {
13377             return LoadPosition(f, n, title);
13378         }
13379     }
13380 }
13381
13382 /* Load the nth position from the given open file, and close it */
13383 int
13384 LoadPosition (FILE *f, int positionNumber, char *title)
13385 {
13386     char *p, line[MSG_SIZ];
13387     Board initial_position;
13388     int i, j, fenMode, pn;
13389
13390     if (gameMode == Training )
13391         SetTrainingModeOff();
13392
13393     if (gameMode != BeginningOfGame) {
13394         Reset(FALSE, TRUE);
13395     }
13396     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13397         fclose(lastLoadPositionFP);
13398     }
13399     if (positionNumber == 0) positionNumber = 1;
13400     lastLoadPositionFP = f;
13401     lastLoadPositionNumber = positionNumber;
13402     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13403     if (first.pr == NoProc && !appData.noChessProgram) {
13404       StartChessProgram(&first);
13405       InitChessProgram(&first, FALSE);
13406     }
13407     pn = positionNumber;
13408     if (positionNumber < 0) {
13409         /* Negative position number means to seek to that byte offset */
13410         if (fseek(f, -positionNumber, 0) == -1) {
13411             DisplayError(_("Can't seek on position file"), 0);
13412             return FALSE;
13413         };
13414         pn = 1;
13415     } else {
13416         if (fseek(f, 0, 0) == -1) {
13417             if (f == lastLoadPositionFP ?
13418                 positionNumber == lastLoadPositionNumber + 1 :
13419                 positionNumber == 1) {
13420                 pn = 1;
13421             } else {
13422                 DisplayError(_("Can't seek on position file"), 0);
13423                 return FALSE;
13424             }
13425         }
13426     }
13427     /* See if this file is FEN or old-style xboard */
13428     if (fgets(line, MSG_SIZ, f) == NULL) {
13429         DisplayError(_("Position not found in file"), 0);
13430         return FALSE;
13431     }
13432     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13433     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13434
13435     if (pn >= 2) {
13436         if (fenMode || line[0] == '#') pn--;
13437         while (pn > 0) {
13438             /* skip positions before number pn */
13439             if (fgets(line, MSG_SIZ, f) == NULL) {
13440                 Reset(TRUE, TRUE);
13441                 DisplayError(_("Position not found in file"), 0);
13442                 return FALSE;
13443             }
13444             if (fenMode || line[0] == '#') pn--;
13445         }
13446     }
13447
13448     if (fenMode) {
13449         char *p;
13450         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13451             DisplayError(_("Bad FEN position in file"), 0);
13452             return FALSE;
13453         }
13454         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13455             sscanf(p+3, "%s", bestMove);
13456         } else *bestMove = NULLCHAR;
13457     } else {
13458         (void) fgets(line, MSG_SIZ, f);
13459         (void) fgets(line, MSG_SIZ, f);
13460
13461         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13462             (void) fgets(line, MSG_SIZ, f);
13463             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13464                 if (*p == ' ')
13465                   continue;
13466                 initial_position[i][j++] = CharToPiece(*p);
13467             }
13468         }
13469
13470         blackPlaysFirst = FALSE;
13471         if (!feof(f)) {
13472             (void) fgets(line, MSG_SIZ, f);
13473             if (strncmp(line, "black", strlen("black"))==0)
13474               blackPlaysFirst = TRUE;
13475         }
13476     }
13477     startedFromSetupPosition = TRUE;
13478
13479     CopyBoard(boards[0], initial_position);
13480     if (blackPlaysFirst) {
13481         currentMove = forwardMostMove = backwardMostMove = 1;
13482         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13483         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13484         CopyBoard(boards[1], initial_position);
13485         DisplayMessage("", _("Black to play"));
13486     } else {
13487         currentMove = forwardMostMove = backwardMostMove = 0;
13488         DisplayMessage("", _("White to play"));
13489     }
13490     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13491     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13492         SendToProgram("force\n", &first);
13493         SendBoard(&first, forwardMostMove);
13494     }
13495     if (appData.debugMode) {
13496 int i, j;
13497   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13498   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13499         fprintf(debugFP, "Load Position\n");
13500     }
13501
13502     if (positionNumber > 1) {
13503       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13504         DisplayTitle(line);
13505     } else {
13506         DisplayTitle(title);
13507     }
13508     gameMode = EditGame;
13509     ModeHighlight();
13510     ResetClocks();
13511     timeRemaining[0][1] = whiteTimeRemaining;
13512     timeRemaining[1][1] = blackTimeRemaining;
13513     DrawPosition(FALSE, boards[currentMove]);
13514
13515     return TRUE;
13516 }
13517
13518
13519 void
13520 CopyPlayerNameIntoFileName (char **dest, char *src)
13521 {
13522     while (*src != NULLCHAR && *src != ',') {
13523         if (*src == ' ') {
13524             *(*dest)++ = '_';
13525             src++;
13526         } else {
13527             *(*dest)++ = *src++;
13528         }
13529     }
13530 }
13531
13532 char *
13533 DefaultFileName (char *ext)
13534 {
13535     static char def[MSG_SIZ];
13536     char *p;
13537
13538     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13539         p = def;
13540         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13541         *p++ = '-';
13542         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13543         *p++ = '.';
13544         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13545     } else {
13546         def[0] = NULLCHAR;
13547     }
13548     return def;
13549 }
13550
13551 /* Save the current game to the given file */
13552 int
13553 SaveGameToFile (char *filename, int append)
13554 {
13555     FILE *f;
13556     char buf[MSG_SIZ];
13557     int result, i, t,tot=0;
13558
13559     if (strcmp(filename, "-") == 0) {
13560         return SaveGame(stdout, 0, NULL);
13561     } else {
13562         for(i=0; i<10; i++) { // upto 10 tries
13563              f = fopen(filename, append ? "a" : "w");
13564              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13565              if(f || errno != 13) break;
13566              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13567              tot += t;
13568         }
13569         if (f == NULL) {
13570             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13571             DisplayError(buf, errno);
13572             return FALSE;
13573         } else {
13574             safeStrCpy(buf, lastMsg, MSG_SIZ);
13575             DisplayMessage(_("Waiting for access to save file"), "");
13576             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13577             DisplayMessage(_("Saving game"), "");
13578             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13579             result = SaveGame(f, 0, NULL);
13580             DisplayMessage(buf, "");
13581             return result;
13582         }
13583     }
13584 }
13585
13586 char *
13587 SavePart (char *str)
13588 {
13589     static char buf[MSG_SIZ];
13590     char *p;
13591
13592     p = strchr(str, ' ');
13593     if (p == NULL) return str;
13594     strncpy(buf, str, p - str);
13595     buf[p - str] = NULLCHAR;
13596     return buf;
13597 }
13598
13599 #define PGN_MAX_LINE 75
13600
13601 #define PGN_SIDE_WHITE  0
13602 #define PGN_SIDE_BLACK  1
13603
13604 static int
13605 FindFirstMoveOutOfBook (int side)
13606 {
13607     int result = -1;
13608
13609     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13610         int index = backwardMostMove;
13611         int has_book_hit = 0;
13612
13613         if( (index % 2) != side ) {
13614             index++;
13615         }
13616
13617         while( index < forwardMostMove ) {
13618             /* Check to see if engine is in book */
13619             int depth = pvInfoList[index].depth;
13620             int score = pvInfoList[index].score;
13621             int in_book = 0;
13622
13623             if( depth <= 2 ) {
13624                 in_book = 1;
13625             }
13626             else if( score == 0 && depth == 63 ) {
13627                 in_book = 1; /* Zappa */
13628             }
13629             else if( score == 2 && depth == 99 ) {
13630                 in_book = 1; /* Abrok */
13631             }
13632
13633             has_book_hit += in_book;
13634
13635             if( ! in_book ) {
13636                 result = index;
13637
13638                 break;
13639             }
13640
13641             index += 2;
13642         }
13643     }
13644
13645     return result;
13646 }
13647
13648 void
13649 GetOutOfBookInfo (char * buf)
13650 {
13651     int oob[2];
13652     int i;
13653     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13654
13655     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13656     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13657
13658     *buf = '\0';
13659
13660     if( oob[0] >= 0 || oob[1] >= 0 ) {
13661         for( i=0; i<2; i++ ) {
13662             int idx = oob[i];
13663
13664             if( idx >= 0 ) {
13665                 if( i > 0 && oob[0] >= 0 ) {
13666                     strcat( buf, "   " );
13667                 }
13668
13669                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13670                 sprintf( buf+strlen(buf), "%s%.2f",
13671                     pvInfoList[idx].score >= 0 ? "+" : "",
13672                     pvInfoList[idx].score / 100.0 );
13673             }
13674         }
13675     }
13676 }
13677
13678 /* Save game in PGN style */
13679 static void
13680 SaveGamePGN2 (FILE *f)
13681 {
13682     int i, offset, linelen, newblock;
13683 //    char *movetext;
13684     char numtext[32];
13685     int movelen, numlen, blank;
13686     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13687
13688     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13689
13690     PrintPGNTags(f, &gameInfo);
13691
13692     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13693
13694     if (backwardMostMove > 0 || startedFromSetupPosition) {
13695         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13696         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13697         fprintf(f, "\n{--------------\n");
13698         PrintPosition(f, backwardMostMove);
13699         fprintf(f, "--------------}\n");
13700         free(fen);
13701     }
13702     else {
13703         /* [AS] Out of book annotation */
13704         if( appData.saveOutOfBookInfo ) {
13705             char buf[64];
13706
13707             GetOutOfBookInfo( buf );
13708
13709             if( buf[0] != '\0' ) {
13710                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13711             }
13712         }
13713
13714         fprintf(f, "\n");
13715     }
13716
13717     i = backwardMostMove;
13718     linelen = 0;
13719     newblock = TRUE;
13720
13721     while (i < forwardMostMove) {
13722         /* Print comments preceding this move */
13723         if (commentList[i] != NULL) {
13724             if (linelen > 0) fprintf(f, "\n");
13725             fprintf(f, "%s", commentList[i]);
13726             linelen = 0;
13727             newblock = TRUE;
13728         }
13729
13730         /* Format move number */
13731         if ((i % 2) == 0)
13732           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13733         else
13734           if (newblock)
13735             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13736           else
13737             numtext[0] = NULLCHAR;
13738
13739         numlen = strlen(numtext);
13740         newblock = FALSE;
13741
13742         /* Print move number */
13743         blank = linelen > 0 && numlen > 0;
13744         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13745             fprintf(f, "\n");
13746             linelen = 0;
13747             blank = 0;
13748         }
13749         if (blank) {
13750             fprintf(f, " ");
13751             linelen++;
13752         }
13753         fprintf(f, "%s", numtext);
13754         linelen += numlen;
13755
13756         /* Get move */
13757         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13758         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13759
13760         /* Print move */
13761         blank = linelen > 0 && movelen > 0;
13762         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13763             fprintf(f, "\n");
13764             linelen = 0;
13765             blank = 0;
13766         }
13767         if (blank) {
13768             fprintf(f, " ");
13769             linelen++;
13770         }
13771         fprintf(f, "%s", move_buffer);
13772         linelen += movelen;
13773
13774         /* [AS] Add PV info if present */
13775         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13776             /* [HGM] add time */
13777             char buf[MSG_SIZ]; int seconds;
13778
13779             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13780
13781             if( seconds <= 0)
13782               buf[0] = 0;
13783             else
13784               if( seconds < 30 )
13785                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13786               else
13787                 {
13788                   seconds = (seconds + 4)/10; // round to full seconds
13789                   if( seconds < 60 )
13790                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13791                   else
13792                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13793                 }
13794
13795             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13796                       pvInfoList[i].score >= 0 ? "+" : "",
13797                       pvInfoList[i].score / 100.0,
13798                       pvInfoList[i].depth,
13799                       buf );
13800
13801             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13802
13803             /* Print score/depth */
13804             blank = linelen > 0 && movelen > 0;
13805             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13806                 fprintf(f, "\n");
13807                 linelen = 0;
13808                 blank = 0;
13809             }
13810             if (blank) {
13811                 fprintf(f, " ");
13812                 linelen++;
13813             }
13814             fprintf(f, "%s", move_buffer);
13815             linelen += movelen;
13816         }
13817
13818         i++;
13819     }
13820
13821     /* Start a new line */
13822     if (linelen > 0) fprintf(f, "\n");
13823
13824     /* Print comments after last move */
13825     if (commentList[i] != NULL) {
13826         fprintf(f, "%s\n", commentList[i]);
13827     }
13828
13829     /* Print result */
13830     if (gameInfo.resultDetails != NULL &&
13831         gameInfo.resultDetails[0] != NULLCHAR) {
13832         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13833         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13834            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13835             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13836         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13837     } else {
13838         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13839     }
13840 }
13841
13842 /* Save game in PGN style and close the file */
13843 int
13844 SaveGamePGN (FILE *f)
13845 {
13846     SaveGamePGN2(f);
13847     fclose(f);
13848     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13849     return TRUE;
13850 }
13851
13852 /* Save game in old style and close the file */
13853 int
13854 SaveGameOldStyle (FILE *f)
13855 {
13856     int i, offset;
13857     time_t tm;
13858
13859     tm = time((time_t *) NULL);
13860
13861     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13862     PrintOpponents(f);
13863
13864     if (backwardMostMove > 0 || startedFromSetupPosition) {
13865         fprintf(f, "\n[--------------\n");
13866         PrintPosition(f, backwardMostMove);
13867         fprintf(f, "--------------]\n");
13868     } else {
13869         fprintf(f, "\n");
13870     }
13871
13872     i = backwardMostMove;
13873     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13874
13875     while (i < forwardMostMove) {
13876         if (commentList[i] != NULL) {
13877             fprintf(f, "[%s]\n", commentList[i]);
13878         }
13879
13880         if ((i % 2) == 1) {
13881             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13882             i++;
13883         } else {
13884             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13885             i++;
13886             if (commentList[i] != NULL) {
13887                 fprintf(f, "\n");
13888                 continue;
13889             }
13890             if (i >= forwardMostMove) {
13891                 fprintf(f, "\n");
13892                 break;
13893             }
13894             fprintf(f, "%s\n", parseList[i]);
13895             i++;
13896         }
13897     }
13898
13899     if (commentList[i] != NULL) {
13900         fprintf(f, "[%s]\n", commentList[i]);
13901     }
13902
13903     /* This isn't really the old style, but it's close enough */
13904     if (gameInfo.resultDetails != NULL &&
13905         gameInfo.resultDetails[0] != NULLCHAR) {
13906         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13907                 gameInfo.resultDetails);
13908     } else {
13909         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13910     }
13911
13912     fclose(f);
13913     return TRUE;
13914 }
13915
13916 /* Save the current game to open file f and close the file */
13917 int
13918 SaveGame (FILE *f, int dummy, char *dummy2)
13919 {
13920     if (gameMode == EditPosition) EditPositionDone(TRUE);
13921     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13922     if (appData.oldSaveStyle)
13923       return SaveGameOldStyle(f);
13924     else
13925       return SaveGamePGN(f);
13926 }
13927
13928 /* Save the current position to the given file */
13929 int
13930 SavePositionToFile (char *filename)
13931 {
13932     FILE *f;
13933     char buf[MSG_SIZ];
13934
13935     if (strcmp(filename, "-") == 0) {
13936         return SavePosition(stdout, 0, NULL);
13937     } else {
13938         f = fopen(filename, "a");
13939         if (f == NULL) {
13940             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13941             DisplayError(buf, errno);
13942             return FALSE;
13943         } else {
13944             safeStrCpy(buf, lastMsg, MSG_SIZ);
13945             DisplayMessage(_("Waiting for access to save file"), "");
13946             flock(fileno(f), LOCK_EX); // [HGM] lock
13947             DisplayMessage(_("Saving position"), "");
13948             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13949             SavePosition(f, 0, NULL);
13950             DisplayMessage(buf, "");
13951             return TRUE;
13952         }
13953     }
13954 }
13955
13956 /* Save the current position to the given open file and close the file */
13957 int
13958 SavePosition (FILE *f, int dummy, char *dummy2)
13959 {
13960     time_t tm;
13961     char *fen;
13962
13963     if (gameMode == EditPosition) EditPositionDone(TRUE);
13964     if (appData.oldSaveStyle) {
13965         tm = time((time_t *) NULL);
13966
13967         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13968         PrintOpponents(f);
13969         fprintf(f, "[--------------\n");
13970         PrintPosition(f, currentMove);
13971         fprintf(f, "--------------]\n");
13972     } else {
13973         fen = PositionToFEN(currentMove, NULL, 1);
13974         fprintf(f, "%s\n", fen);
13975         free(fen);
13976     }
13977     fclose(f);
13978     return TRUE;
13979 }
13980
13981 void
13982 ReloadCmailMsgEvent (int unregister)
13983 {
13984 #if !WIN32
13985     static char *inFilename = NULL;
13986     static char *outFilename;
13987     int i;
13988     struct stat inbuf, outbuf;
13989     int status;
13990
13991     /* Any registered moves are unregistered if unregister is set, */
13992     /* i.e. invoked by the signal handler */
13993     if (unregister) {
13994         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13995             cmailMoveRegistered[i] = FALSE;
13996             if (cmailCommentList[i] != NULL) {
13997                 free(cmailCommentList[i]);
13998                 cmailCommentList[i] = NULL;
13999             }
14000         }
14001         nCmailMovesRegistered = 0;
14002     }
14003
14004     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14005         cmailResult[i] = CMAIL_NOT_RESULT;
14006     }
14007     nCmailResults = 0;
14008
14009     if (inFilename == NULL) {
14010         /* Because the filenames are static they only get malloced once  */
14011         /* and they never get freed                                      */
14012         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14013         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14014
14015         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14016         sprintf(outFilename, "%s.out", appData.cmailGameName);
14017     }
14018
14019     status = stat(outFilename, &outbuf);
14020     if (status < 0) {
14021         cmailMailedMove = FALSE;
14022     } else {
14023         status = stat(inFilename, &inbuf);
14024         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14025     }
14026
14027     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14028        counts the games, notes how each one terminated, etc.
14029
14030        It would be nice to remove this kludge and instead gather all
14031        the information while building the game list.  (And to keep it
14032        in the game list nodes instead of having a bunch of fixed-size
14033        parallel arrays.)  Note this will require getting each game's
14034        termination from the PGN tags, as the game list builder does
14035        not process the game moves.  --mann
14036        */
14037     cmailMsgLoaded = TRUE;
14038     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14039
14040     /* Load first game in the file or popup game menu */
14041     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14042
14043 #endif /* !WIN32 */
14044     return;
14045 }
14046
14047 int
14048 RegisterMove ()
14049 {
14050     FILE *f;
14051     char string[MSG_SIZ];
14052
14053     if (   cmailMailedMove
14054         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14055         return TRUE;            /* Allow free viewing  */
14056     }
14057
14058     /* Unregister move to ensure that we don't leave RegisterMove        */
14059     /* with the move registered when the conditions for registering no   */
14060     /* longer hold                                                       */
14061     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14062         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14063         nCmailMovesRegistered --;
14064
14065         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14066           {
14067               free(cmailCommentList[lastLoadGameNumber - 1]);
14068               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14069           }
14070     }
14071
14072     if (cmailOldMove == -1) {
14073         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14074         return FALSE;
14075     }
14076
14077     if (currentMove > cmailOldMove + 1) {
14078         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14079         return FALSE;
14080     }
14081
14082     if (currentMove < cmailOldMove) {
14083         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14084         return FALSE;
14085     }
14086
14087     if (forwardMostMove > currentMove) {
14088         /* Silently truncate extra moves */
14089         TruncateGame();
14090     }
14091
14092     if (   (currentMove == cmailOldMove + 1)
14093         || (   (currentMove == cmailOldMove)
14094             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14095                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14096         if (gameInfo.result != GameUnfinished) {
14097             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14098         }
14099
14100         if (commentList[currentMove] != NULL) {
14101             cmailCommentList[lastLoadGameNumber - 1]
14102               = StrSave(commentList[currentMove]);
14103         }
14104         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14105
14106         if (appData.debugMode)
14107           fprintf(debugFP, "Saving %s for game %d\n",
14108                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14109
14110         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14111
14112         f = fopen(string, "w");
14113         if (appData.oldSaveStyle) {
14114             SaveGameOldStyle(f); /* also closes the file */
14115
14116             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14117             f = fopen(string, "w");
14118             SavePosition(f, 0, NULL); /* also closes the file */
14119         } else {
14120             fprintf(f, "{--------------\n");
14121             PrintPosition(f, currentMove);
14122             fprintf(f, "--------------}\n\n");
14123
14124             SaveGame(f, 0, NULL); /* also closes the file*/
14125         }
14126
14127         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14128         nCmailMovesRegistered ++;
14129     } else if (nCmailGames == 1) {
14130         DisplayError(_("You have not made a move yet"), 0);
14131         return FALSE;
14132     }
14133
14134     return TRUE;
14135 }
14136
14137 void
14138 MailMoveEvent ()
14139 {
14140 #if !WIN32
14141     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14142     FILE *commandOutput;
14143     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14144     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14145     int nBuffers;
14146     int i;
14147     int archived;
14148     char *arcDir;
14149
14150     if (! cmailMsgLoaded) {
14151         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14152         return;
14153     }
14154
14155     if (nCmailGames == nCmailResults) {
14156         DisplayError(_("No unfinished games"), 0);
14157         return;
14158     }
14159
14160 #if CMAIL_PROHIBIT_REMAIL
14161     if (cmailMailedMove) {
14162       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);
14163         DisplayError(msg, 0);
14164         return;
14165     }
14166 #endif
14167
14168     if (! (cmailMailedMove || RegisterMove())) return;
14169
14170     if (   cmailMailedMove
14171         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14172       snprintf(string, MSG_SIZ, partCommandString,
14173                appData.debugMode ? " -v" : "", appData.cmailGameName);
14174         commandOutput = popen(string, "r");
14175
14176         if (commandOutput == NULL) {
14177             DisplayError(_("Failed to invoke cmail"), 0);
14178         } else {
14179             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14180                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14181             }
14182             if (nBuffers > 1) {
14183                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14184                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14185                 nBytes = MSG_SIZ - 1;
14186             } else {
14187                 (void) memcpy(msg, buffer, nBytes);
14188             }
14189             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14190
14191             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14192                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14193
14194                 archived = TRUE;
14195                 for (i = 0; i < nCmailGames; i ++) {
14196                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14197                         archived = FALSE;
14198                     }
14199                 }
14200                 if (   archived
14201                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14202                         != NULL)) {
14203                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14204                            arcDir,
14205                            appData.cmailGameName,
14206                            gameInfo.date);
14207                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14208                     cmailMsgLoaded = FALSE;
14209                 }
14210             }
14211
14212             DisplayInformation(msg);
14213             pclose(commandOutput);
14214         }
14215     } else {
14216         if ((*cmailMsg) != '\0') {
14217             DisplayInformation(cmailMsg);
14218         }
14219     }
14220
14221     return;
14222 #endif /* !WIN32 */
14223 }
14224
14225 char *
14226 CmailMsg ()
14227 {
14228 #if WIN32
14229     return NULL;
14230 #else
14231     int  prependComma = 0;
14232     char number[5];
14233     char string[MSG_SIZ];       /* Space for game-list */
14234     int  i;
14235
14236     if (!cmailMsgLoaded) return "";
14237
14238     if (cmailMailedMove) {
14239       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14240     } else {
14241         /* Create a list of games left */
14242       snprintf(string, MSG_SIZ, "[");
14243         for (i = 0; i < nCmailGames; i ++) {
14244             if (! (   cmailMoveRegistered[i]
14245                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14246                 if (prependComma) {
14247                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14248                 } else {
14249                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14250                     prependComma = 1;
14251                 }
14252
14253                 strcat(string, number);
14254             }
14255         }
14256         strcat(string, "]");
14257
14258         if (nCmailMovesRegistered + nCmailResults == 0) {
14259             switch (nCmailGames) {
14260               case 1:
14261                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14262                 break;
14263
14264               case 2:
14265                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14266                 break;
14267
14268               default:
14269                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14270                          nCmailGames);
14271                 break;
14272             }
14273         } else {
14274             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14275               case 1:
14276                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14277                          string);
14278                 break;
14279
14280               case 0:
14281                 if (nCmailResults == nCmailGames) {
14282                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14283                 } else {
14284                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14285                 }
14286                 break;
14287
14288               default:
14289                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14290                          string);
14291             }
14292         }
14293     }
14294     return cmailMsg;
14295 #endif /* WIN32 */
14296 }
14297
14298 void
14299 ResetGameEvent ()
14300 {
14301     if (gameMode == Training)
14302       SetTrainingModeOff();
14303
14304     Reset(TRUE, TRUE);
14305     cmailMsgLoaded = FALSE;
14306     if (appData.icsActive) {
14307       SendToICS(ics_prefix);
14308       SendToICS("refresh\n");
14309     }
14310 }
14311
14312 void
14313 ExitEvent (int status)
14314 {
14315     exiting++;
14316     if (exiting > 2) {
14317       /* Give up on clean exit */
14318       exit(status);
14319     }
14320     if (exiting > 1) {
14321       /* Keep trying for clean exit */
14322       return;
14323     }
14324
14325     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14326     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14327
14328     if (telnetISR != NULL) {
14329       RemoveInputSource(telnetISR);
14330     }
14331     if (icsPR != NoProc) {
14332       DestroyChildProcess(icsPR, TRUE);
14333     }
14334
14335     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14336     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14337
14338     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14339     /* make sure this other one finishes before killing it!                  */
14340     if(endingGame) { int count = 0;
14341         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14342         while(endingGame && count++ < 10) DoSleep(1);
14343         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14344     }
14345
14346     /* Kill off chess programs */
14347     if (first.pr != NoProc) {
14348         ExitAnalyzeMode();
14349
14350         DoSleep( appData.delayBeforeQuit );
14351         SendToProgram("quit\n", &first);
14352         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14353     }
14354     if (second.pr != NoProc) {
14355         DoSleep( appData.delayBeforeQuit );
14356         SendToProgram("quit\n", &second);
14357         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14358     }
14359     if (first.isr != NULL) {
14360         RemoveInputSource(first.isr);
14361     }
14362     if (second.isr != NULL) {
14363         RemoveInputSource(second.isr);
14364     }
14365
14366     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14367     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14368
14369     ShutDownFrontEnd();
14370     exit(status);
14371 }
14372
14373 void
14374 PauseEngine (ChessProgramState *cps)
14375 {
14376     SendToProgram("pause\n", cps);
14377     cps->pause = 2;
14378 }
14379
14380 void
14381 UnPauseEngine (ChessProgramState *cps)
14382 {
14383     SendToProgram("resume\n", cps);
14384     cps->pause = 1;
14385 }
14386
14387 void
14388 PauseEvent ()
14389 {
14390     if (appData.debugMode)
14391         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14392     if (pausing) {
14393         pausing = FALSE;
14394         ModeHighlight();
14395         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14396             StartClocks();
14397             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14398                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14399                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14400             }
14401             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14402             HandleMachineMove(stashedInputMove, stalledEngine);
14403             stalledEngine = NULL;
14404             return;
14405         }
14406         if (gameMode == MachinePlaysWhite ||
14407             gameMode == TwoMachinesPlay   ||
14408             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14409             if(first.pause)  UnPauseEngine(&first);
14410             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14411             if(second.pause) UnPauseEngine(&second);
14412             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14413             StartClocks();
14414         } else {
14415             DisplayBothClocks();
14416         }
14417         if (gameMode == PlayFromGameFile) {
14418             if (appData.timeDelay >= 0)
14419                 AutoPlayGameLoop();
14420         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14421             Reset(FALSE, TRUE);
14422             SendToICS(ics_prefix);
14423             SendToICS("refresh\n");
14424         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14425             ForwardInner(forwardMostMove);
14426         }
14427         pauseExamInvalid = FALSE;
14428     } else {
14429         switch (gameMode) {
14430           default:
14431             return;
14432           case IcsExamining:
14433             pauseExamForwardMostMove = forwardMostMove;
14434             pauseExamInvalid = FALSE;
14435             /* fall through */
14436           case IcsObserving:
14437           case IcsPlayingWhite:
14438           case IcsPlayingBlack:
14439             pausing = TRUE;
14440             ModeHighlight();
14441             return;
14442           case PlayFromGameFile:
14443             (void) StopLoadGameTimer();
14444             pausing = TRUE;
14445             ModeHighlight();
14446             break;
14447           case BeginningOfGame:
14448             if (appData.icsActive) return;
14449             /* else fall through */
14450           case MachinePlaysWhite:
14451           case MachinePlaysBlack:
14452           case TwoMachinesPlay:
14453             if (forwardMostMove == 0)
14454               return;           /* don't pause if no one has moved */
14455             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14456                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14457                 if(onMove->pause) {           // thinking engine can be paused
14458                     PauseEngine(onMove);      // do it
14459                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14460                         PauseEngine(onMove->other);
14461                     else
14462                         SendToProgram("easy\n", onMove->other);
14463                     StopClocks();
14464                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14465             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14466                 if(first.pause) {
14467                     PauseEngine(&first);
14468                     StopClocks();
14469                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14470             } else { // human on move, pause pondering by either method
14471                 if(first.pause)
14472                     PauseEngine(&first);
14473                 else if(appData.ponderNextMove)
14474                     SendToProgram("easy\n", &first);
14475                 StopClocks();
14476             }
14477             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14478           case AnalyzeMode:
14479             pausing = TRUE;
14480             ModeHighlight();
14481             break;
14482         }
14483     }
14484 }
14485
14486 void
14487 EditCommentEvent ()
14488 {
14489     char title[MSG_SIZ];
14490
14491     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14492       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14493     } else {
14494       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14495                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14496                parseList[currentMove - 1]);
14497     }
14498
14499     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14500 }
14501
14502
14503 void
14504 EditTagsEvent ()
14505 {
14506     char *tags = PGNTags(&gameInfo);
14507     bookUp = FALSE;
14508     EditTagsPopUp(tags, NULL);
14509     free(tags);
14510 }
14511
14512 void
14513 ToggleSecond ()
14514 {
14515   if(second.analyzing) {
14516     SendToProgram("exit\n", &second);
14517     second.analyzing = FALSE;
14518   } else {
14519     if (second.pr == NoProc) StartChessProgram(&second);
14520     InitChessProgram(&second, FALSE);
14521     FeedMovesToProgram(&second, currentMove);
14522
14523     SendToProgram("analyze\n", &second);
14524     second.analyzing = TRUE;
14525   }
14526 }
14527
14528 /* Toggle ShowThinking */
14529 void
14530 ToggleShowThinking()
14531 {
14532   appData.showThinking = !appData.showThinking;
14533   ShowThinkingEvent();
14534 }
14535
14536 int
14537 AnalyzeModeEvent ()
14538 {
14539     char buf[MSG_SIZ];
14540
14541     if (!first.analysisSupport) {
14542       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14543       DisplayError(buf, 0);
14544       return 0;
14545     }
14546     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14547     if (appData.icsActive) {
14548         if (gameMode != IcsObserving) {
14549           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14550             DisplayError(buf, 0);
14551             /* secure check */
14552             if (appData.icsEngineAnalyze) {
14553                 if (appData.debugMode)
14554                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14555                 ExitAnalyzeMode();
14556                 ModeHighlight();
14557             }
14558             return 0;
14559         }
14560         /* if enable, user wants to disable icsEngineAnalyze */
14561         if (appData.icsEngineAnalyze) {
14562                 ExitAnalyzeMode();
14563                 ModeHighlight();
14564                 return 0;
14565         }
14566         appData.icsEngineAnalyze = TRUE;
14567         if (appData.debugMode)
14568             fprintf(debugFP, "ICS engine analyze starting... \n");
14569     }
14570
14571     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14572     if (appData.noChessProgram || gameMode == AnalyzeMode)
14573       return 0;
14574
14575     if (gameMode != AnalyzeFile) {
14576         if (!appData.icsEngineAnalyze) {
14577                EditGameEvent();
14578                if (gameMode != EditGame) return 0;
14579         }
14580         if (!appData.showThinking) ToggleShowThinking();
14581         ResurrectChessProgram();
14582         SendToProgram("analyze\n", &first);
14583         first.analyzing = TRUE;
14584         /*first.maybeThinking = TRUE;*/
14585         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14586         EngineOutputPopUp();
14587     }
14588     if (!appData.icsEngineAnalyze) {
14589         gameMode = AnalyzeMode;
14590         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14591     }
14592     pausing = FALSE;
14593     ModeHighlight();
14594     SetGameInfo();
14595
14596     StartAnalysisClock();
14597     GetTimeMark(&lastNodeCountTime);
14598     lastNodeCount = 0;
14599     return 1;
14600 }
14601
14602 void
14603 AnalyzeFileEvent ()
14604 {
14605     if (appData.noChessProgram || gameMode == AnalyzeFile)
14606       return;
14607
14608     if (!first.analysisSupport) {
14609       char buf[MSG_SIZ];
14610       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14611       DisplayError(buf, 0);
14612       return;
14613     }
14614
14615     if (gameMode != AnalyzeMode) {
14616         keepInfo = 1; // mere annotating should not alter PGN tags
14617         EditGameEvent();
14618         keepInfo = 0;
14619         if (gameMode != EditGame) return;
14620         if (!appData.showThinking) ToggleShowThinking();
14621         ResurrectChessProgram();
14622         SendToProgram("analyze\n", &first);
14623         first.analyzing = TRUE;
14624         /*first.maybeThinking = TRUE;*/
14625         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14626         EngineOutputPopUp();
14627     }
14628     gameMode = AnalyzeFile;
14629     pausing = FALSE;
14630     ModeHighlight();
14631
14632     StartAnalysisClock();
14633     GetTimeMark(&lastNodeCountTime);
14634     lastNodeCount = 0;
14635     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14636     AnalysisPeriodicEvent(1);
14637 }
14638
14639 void
14640 MachineWhiteEvent ()
14641 {
14642     char buf[MSG_SIZ];
14643     char *bookHit = NULL;
14644
14645     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14646       return;
14647
14648
14649     if (gameMode == PlayFromGameFile ||
14650         gameMode == TwoMachinesPlay  ||
14651         gameMode == Training         ||
14652         gameMode == AnalyzeMode      ||
14653         gameMode == EndOfGame)
14654         EditGameEvent();
14655
14656     if (gameMode == EditPosition)
14657         EditPositionDone(TRUE);
14658
14659     if (!WhiteOnMove(currentMove)) {
14660         DisplayError(_("It is not White's turn"), 0);
14661         return;
14662     }
14663
14664     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14665       ExitAnalyzeMode();
14666
14667     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14668         gameMode == AnalyzeFile)
14669         TruncateGame();
14670
14671     ResurrectChessProgram();    /* in case it isn't running */
14672     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14673         gameMode = MachinePlaysWhite;
14674         ResetClocks();
14675     } else
14676     gameMode = MachinePlaysWhite;
14677     pausing = FALSE;
14678     ModeHighlight();
14679     SetGameInfo();
14680     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14681     DisplayTitle(buf);
14682     if (first.sendName) {
14683       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14684       SendToProgram(buf, &first);
14685     }
14686     if (first.sendTime) {
14687       if (first.useColors) {
14688         SendToProgram("black\n", &first); /*gnu kludge*/
14689       }
14690       SendTimeRemaining(&first, TRUE);
14691     }
14692     if (first.useColors) {
14693       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14694     }
14695     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14696     SetMachineThinkingEnables();
14697     first.maybeThinking = TRUE;
14698     StartClocks();
14699     firstMove = FALSE;
14700
14701     if (appData.autoFlipView && !flipView) {
14702       flipView = !flipView;
14703       DrawPosition(FALSE, NULL);
14704       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14705     }
14706
14707     if(bookHit) { // [HGM] book: simulate book reply
14708         static char bookMove[MSG_SIZ]; // a bit generous?
14709
14710         programStats.nodes = programStats.depth = programStats.time =
14711         programStats.score = programStats.got_only_move = 0;
14712         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14713
14714         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14715         strcat(bookMove, bookHit);
14716         HandleMachineMove(bookMove, &first);
14717     }
14718 }
14719
14720 void
14721 MachineBlackEvent ()
14722 {
14723   char buf[MSG_SIZ];
14724   char *bookHit = NULL;
14725
14726     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14727         return;
14728
14729
14730     if (gameMode == PlayFromGameFile ||
14731         gameMode == TwoMachinesPlay  ||
14732         gameMode == Training         ||
14733         gameMode == AnalyzeMode      ||
14734         gameMode == EndOfGame)
14735         EditGameEvent();
14736
14737     if (gameMode == EditPosition)
14738         EditPositionDone(TRUE);
14739
14740     if (WhiteOnMove(currentMove)) {
14741         DisplayError(_("It is not Black's turn"), 0);
14742         return;
14743     }
14744
14745     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14746       ExitAnalyzeMode();
14747
14748     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14749         gameMode == AnalyzeFile)
14750         TruncateGame();
14751
14752     ResurrectChessProgram();    /* in case it isn't running */
14753     gameMode = MachinePlaysBlack;
14754     pausing = FALSE;
14755     ModeHighlight();
14756     SetGameInfo();
14757     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14758     DisplayTitle(buf);
14759     if (first.sendName) {
14760       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14761       SendToProgram(buf, &first);
14762     }
14763     if (first.sendTime) {
14764       if (first.useColors) {
14765         SendToProgram("white\n", &first); /*gnu kludge*/
14766       }
14767       SendTimeRemaining(&first, FALSE);
14768     }
14769     if (first.useColors) {
14770       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14771     }
14772     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14773     SetMachineThinkingEnables();
14774     first.maybeThinking = TRUE;
14775     StartClocks();
14776
14777     if (appData.autoFlipView && flipView) {
14778       flipView = !flipView;
14779       DrawPosition(FALSE, NULL);
14780       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14781     }
14782     if(bookHit) { // [HGM] book: simulate book reply
14783         static char bookMove[MSG_SIZ]; // a bit generous?
14784
14785         programStats.nodes = programStats.depth = programStats.time =
14786         programStats.score = programStats.got_only_move = 0;
14787         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14788
14789         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14790         strcat(bookMove, bookHit);
14791         HandleMachineMove(bookMove, &first);
14792     }
14793 }
14794
14795
14796 void
14797 DisplayTwoMachinesTitle ()
14798 {
14799     char buf[MSG_SIZ];
14800     if (appData.matchGames > 0) {
14801         if(appData.tourneyFile[0]) {
14802           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14803                    gameInfo.white, _("vs."), gameInfo.black,
14804                    nextGame+1, appData.matchGames+1,
14805                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14806         } else
14807         if (first.twoMachinesColor[0] == 'w') {
14808           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14809                    gameInfo.white, _("vs."),  gameInfo.black,
14810                    first.matchWins, second.matchWins,
14811                    matchGame - 1 - (first.matchWins + second.matchWins));
14812         } else {
14813           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14814                    gameInfo.white, _("vs."), gameInfo.black,
14815                    second.matchWins, first.matchWins,
14816                    matchGame - 1 - (first.matchWins + second.matchWins));
14817         }
14818     } else {
14819       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14820     }
14821     DisplayTitle(buf);
14822 }
14823
14824 void
14825 SettingsMenuIfReady ()
14826 {
14827   if (second.lastPing != second.lastPong) {
14828     DisplayMessage("", _("Waiting for second chess program"));
14829     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14830     return;
14831   }
14832   ThawUI();
14833   DisplayMessage("", "");
14834   SettingsPopUp(&second);
14835 }
14836
14837 int
14838 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14839 {
14840     char buf[MSG_SIZ];
14841     if (cps->pr == NoProc) {
14842         StartChessProgram(cps);
14843         if (cps->protocolVersion == 1) {
14844           retry();
14845           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14846         } else {
14847           /* kludge: allow timeout for initial "feature" command */
14848           if(retry != TwoMachinesEventIfReady) FreezeUI();
14849           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14850           DisplayMessage("", buf);
14851           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14852         }
14853         return 1;
14854     }
14855     return 0;
14856 }
14857
14858 void
14859 TwoMachinesEvent P((void))
14860 {
14861     int i;
14862     char buf[MSG_SIZ];
14863     ChessProgramState *onmove;
14864     char *bookHit = NULL;
14865     static int stalling = 0;
14866     TimeMark now;
14867     long wait;
14868
14869     if (appData.noChessProgram) return;
14870
14871     switch (gameMode) {
14872       case TwoMachinesPlay:
14873         return;
14874       case MachinePlaysWhite:
14875       case MachinePlaysBlack:
14876         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14877             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14878             return;
14879         }
14880         /* fall through */
14881       case BeginningOfGame:
14882       case PlayFromGameFile:
14883       case EndOfGame:
14884         EditGameEvent();
14885         if (gameMode != EditGame) return;
14886         break;
14887       case EditPosition:
14888         EditPositionDone(TRUE);
14889         break;
14890       case AnalyzeMode:
14891       case AnalyzeFile:
14892         ExitAnalyzeMode();
14893         break;
14894       case EditGame:
14895       default:
14896         break;
14897     }
14898
14899 //    forwardMostMove = currentMove;
14900     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14901     startingEngine = TRUE;
14902
14903     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14904
14905     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14906     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14907       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14908       return;
14909     }
14910     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14911
14912     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14913                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14914         startingEngine = matchMode = FALSE;
14915         DisplayError("second engine does not play this", 0);
14916         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14917         EditGameEvent(); // switch back to EditGame mode
14918         return;
14919     }
14920
14921     if(!stalling) {
14922       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14923       SendToProgram("force\n", &second);
14924       stalling = 1;
14925       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14926       return;
14927     }
14928     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14929     if(appData.matchPause>10000 || appData.matchPause<10)
14930                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14931     wait = SubtractTimeMarks(&now, &pauseStart);
14932     if(wait < appData.matchPause) {
14933         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14934         return;
14935     }
14936     // we are now committed to starting the game
14937     stalling = 0;
14938     DisplayMessage("", "");
14939     if (startedFromSetupPosition) {
14940         SendBoard(&second, backwardMostMove);
14941     if (appData.debugMode) {
14942         fprintf(debugFP, "Two Machines\n");
14943     }
14944     }
14945     for (i = backwardMostMove; i < forwardMostMove; i++) {
14946         SendMoveToProgram(i, &second);
14947     }
14948
14949     gameMode = TwoMachinesPlay;
14950     pausing = startingEngine = FALSE;
14951     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14952     SetGameInfo();
14953     DisplayTwoMachinesTitle();
14954     firstMove = TRUE;
14955     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14956         onmove = &first;
14957     } else {
14958         onmove = &second;
14959     }
14960     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14961     SendToProgram(first.computerString, &first);
14962     if (first.sendName) {
14963       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14964       SendToProgram(buf, &first);
14965     }
14966     SendToProgram(second.computerString, &second);
14967     if (second.sendName) {
14968       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14969       SendToProgram(buf, &second);
14970     }
14971
14972     ResetClocks();
14973     if (!first.sendTime || !second.sendTime) {
14974         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14975         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14976     }
14977     if (onmove->sendTime) {
14978       if (onmove->useColors) {
14979         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14980       }
14981       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14982     }
14983     if (onmove->useColors) {
14984       SendToProgram(onmove->twoMachinesColor, onmove);
14985     }
14986     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14987 //    SendToProgram("go\n", onmove);
14988     onmove->maybeThinking = TRUE;
14989     SetMachineThinkingEnables();
14990
14991     StartClocks();
14992
14993     if(bookHit) { // [HGM] book: simulate book reply
14994         static char bookMove[MSG_SIZ]; // a bit generous?
14995
14996         programStats.nodes = programStats.depth = programStats.time =
14997         programStats.score = programStats.got_only_move = 0;
14998         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14999
15000         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15001         strcat(bookMove, bookHit);
15002         savedMessage = bookMove; // args for deferred call
15003         savedState = onmove;
15004         ScheduleDelayedEvent(DeferredBookMove, 1);
15005     }
15006 }
15007
15008 void
15009 TrainingEvent ()
15010 {
15011     if (gameMode == Training) {
15012       SetTrainingModeOff();
15013       gameMode = PlayFromGameFile;
15014       DisplayMessage("", _("Training mode off"));
15015     } else {
15016       gameMode = Training;
15017       animateTraining = appData.animate;
15018
15019       /* make sure we are not already at the end of the game */
15020       if (currentMove < forwardMostMove) {
15021         SetTrainingModeOn();
15022         DisplayMessage("", _("Training mode on"));
15023       } else {
15024         gameMode = PlayFromGameFile;
15025         DisplayError(_("Already at end of game"), 0);
15026       }
15027     }
15028     ModeHighlight();
15029 }
15030
15031 void
15032 IcsClientEvent ()
15033 {
15034     if (!appData.icsActive) return;
15035     switch (gameMode) {
15036       case IcsPlayingWhite:
15037       case IcsPlayingBlack:
15038       case IcsObserving:
15039       case IcsIdle:
15040       case BeginningOfGame:
15041       case IcsExamining:
15042         return;
15043
15044       case EditGame:
15045         break;
15046
15047       case EditPosition:
15048         EditPositionDone(TRUE);
15049         break;
15050
15051       case AnalyzeMode:
15052       case AnalyzeFile:
15053         ExitAnalyzeMode();
15054         break;
15055
15056       default:
15057         EditGameEvent();
15058         break;
15059     }
15060
15061     gameMode = IcsIdle;
15062     ModeHighlight();
15063     return;
15064 }
15065
15066 void
15067 EditGameEvent ()
15068 {
15069     int i;
15070
15071     switch (gameMode) {
15072       case Training:
15073         SetTrainingModeOff();
15074         break;
15075       case MachinePlaysWhite:
15076       case MachinePlaysBlack:
15077       case BeginningOfGame:
15078         SendToProgram("force\n", &first);
15079         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15080             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15081                 char buf[MSG_SIZ];
15082                 abortEngineThink = TRUE;
15083                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15084                 SendToProgram(buf, &first);
15085                 DisplayMessage("Aborting engine think", "");
15086                 FreezeUI();
15087             }
15088         }
15089         SetUserThinkingEnables();
15090         break;
15091       case PlayFromGameFile:
15092         (void) StopLoadGameTimer();
15093         if (gameFileFP != NULL) {
15094             gameFileFP = NULL;
15095         }
15096         break;
15097       case EditPosition:
15098         EditPositionDone(TRUE);
15099         break;
15100       case AnalyzeMode:
15101       case AnalyzeFile:
15102         ExitAnalyzeMode();
15103         SendToProgram("force\n", &first);
15104         break;
15105       case TwoMachinesPlay:
15106         GameEnds(EndOfFile, NULL, GE_PLAYER);
15107         ResurrectChessProgram();
15108         SetUserThinkingEnables();
15109         break;
15110       case EndOfGame:
15111         ResurrectChessProgram();
15112         break;
15113       case IcsPlayingBlack:
15114       case IcsPlayingWhite:
15115         DisplayError(_("Warning: You are still playing a game"), 0);
15116         break;
15117       case IcsObserving:
15118         DisplayError(_("Warning: You are still observing a game"), 0);
15119         break;
15120       case IcsExamining:
15121         DisplayError(_("Warning: You are still examining a game"), 0);
15122         break;
15123       case IcsIdle:
15124         break;
15125       case EditGame:
15126       default:
15127         return;
15128     }
15129
15130     pausing = FALSE;
15131     StopClocks();
15132     first.offeredDraw = second.offeredDraw = 0;
15133
15134     if (gameMode == PlayFromGameFile) {
15135         whiteTimeRemaining = timeRemaining[0][currentMove];
15136         blackTimeRemaining = timeRemaining[1][currentMove];
15137         DisplayTitle("");
15138     }
15139
15140     if (gameMode == MachinePlaysWhite ||
15141         gameMode == MachinePlaysBlack ||
15142         gameMode == TwoMachinesPlay ||
15143         gameMode == EndOfGame) {
15144         i = forwardMostMove;
15145         while (i > currentMove) {
15146             SendToProgram("undo\n", &first);
15147             i--;
15148         }
15149         if(!adjustedClock) {
15150         whiteTimeRemaining = timeRemaining[0][currentMove];
15151         blackTimeRemaining = timeRemaining[1][currentMove];
15152         DisplayBothClocks();
15153         }
15154         if (whiteFlag || blackFlag) {
15155             whiteFlag = blackFlag = 0;
15156         }
15157         DisplayTitle("");
15158     }
15159
15160     gameMode = EditGame;
15161     ModeHighlight();
15162     SetGameInfo();
15163 }
15164
15165
15166 void
15167 EditPositionEvent ()
15168 {
15169     if (gameMode == EditPosition) {
15170         EditGameEvent();
15171         return;
15172     }
15173
15174     EditGameEvent();
15175     if (gameMode != EditGame) return;
15176
15177     gameMode = EditPosition;
15178     ModeHighlight();
15179     SetGameInfo();
15180     if (currentMove > 0)
15181       CopyBoard(boards[0], boards[currentMove]);
15182
15183     blackPlaysFirst = !WhiteOnMove(currentMove);
15184     ResetClocks();
15185     currentMove = forwardMostMove = backwardMostMove = 0;
15186     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15187     DisplayMove(-1);
15188     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15189 }
15190
15191 void
15192 ExitAnalyzeMode ()
15193 {
15194     /* [DM] icsEngineAnalyze - possible call from other functions */
15195     if (appData.icsEngineAnalyze) {
15196         appData.icsEngineAnalyze = FALSE;
15197
15198         DisplayMessage("",_("Close ICS engine analyze..."));
15199     }
15200     if (first.analysisSupport && first.analyzing) {
15201       SendToBoth("exit\n");
15202       first.analyzing = second.analyzing = FALSE;
15203     }
15204     thinkOutput[0] = NULLCHAR;
15205 }
15206
15207 void
15208 EditPositionDone (Boolean fakeRights)
15209 {
15210     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15211
15212     startedFromSetupPosition = TRUE;
15213     InitChessProgram(&first, FALSE);
15214     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15215       boards[0][EP_STATUS] = EP_NONE;
15216       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15217       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15218         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15219         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15220       } else boards[0][CASTLING][2] = NoRights;
15221       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15222         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15223         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15224       } else boards[0][CASTLING][5] = NoRights;
15225       if(gameInfo.variant == VariantSChess) {
15226         int i;
15227         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15228           boards[0][VIRGIN][i] = 0;
15229           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15230           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15231         }
15232       }
15233     }
15234     SendToProgram("force\n", &first);
15235     if (blackPlaysFirst) {
15236         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15237         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15238         currentMove = forwardMostMove = backwardMostMove = 1;
15239         CopyBoard(boards[1], boards[0]);
15240     } else {
15241         currentMove = forwardMostMove = backwardMostMove = 0;
15242     }
15243     SendBoard(&first, forwardMostMove);
15244     if (appData.debugMode) {
15245         fprintf(debugFP, "EditPosDone\n");
15246     }
15247     DisplayTitle("");
15248     DisplayMessage("", "");
15249     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15250     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15251     gameMode = EditGame;
15252     ModeHighlight();
15253     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15254     ClearHighlights(); /* [AS] */
15255 }
15256
15257 /* Pause for `ms' milliseconds */
15258 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15259 void
15260 TimeDelay (long ms)
15261 {
15262     TimeMark m1, m2;
15263
15264     GetTimeMark(&m1);
15265     do {
15266         GetTimeMark(&m2);
15267     } while (SubtractTimeMarks(&m2, &m1) < ms);
15268 }
15269
15270 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15271 void
15272 SendMultiLineToICS (char *buf)
15273 {
15274     char temp[MSG_SIZ+1], *p;
15275     int len;
15276
15277     len = strlen(buf);
15278     if (len > MSG_SIZ)
15279       len = MSG_SIZ;
15280
15281     strncpy(temp, buf, len);
15282     temp[len] = 0;
15283
15284     p = temp;
15285     while (*p) {
15286         if (*p == '\n' || *p == '\r')
15287           *p = ' ';
15288         ++p;
15289     }
15290
15291     strcat(temp, "\n");
15292     SendToICS(temp);
15293     SendToPlayer(temp, strlen(temp));
15294 }
15295
15296 void
15297 SetWhiteToPlayEvent ()
15298 {
15299     if (gameMode == EditPosition) {
15300         blackPlaysFirst = FALSE;
15301         DisplayBothClocks();    /* works because currentMove is 0 */
15302     } else if (gameMode == IcsExamining) {
15303         SendToICS(ics_prefix);
15304         SendToICS("tomove white\n");
15305     }
15306 }
15307
15308 void
15309 SetBlackToPlayEvent ()
15310 {
15311     if (gameMode == EditPosition) {
15312         blackPlaysFirst = TRUE;
15313         currentMove = 1;        /* kludge */
15314         DisplayBothClocks();
15315         currentMove = 0;
15316     } else if (gameMode == IcsExamining) {
15317         SendToICS(ics_prefix);
15318         SendToICS("tomove black\n");
15319     }
15320 }
15321
15322 void
15323 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15324 {
15325     char buf[MSG_SIZ];
15326     ChessSquare piece = boards[0][y][x];
15327     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15328     static int lastVariant;
15329
15330     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15331
15332     switch (selection) {
15333       case ClearBoard:
15334         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15335         MarkTargetSquares(1);
15336         CopyBoard(currentBoard, boards[0]);
15337         CopyBoard(menuBoard, initialPosition);
15338         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15339             SendToICS(ics_prefix);
15340             SendToICS("bsetup clear\n");
15341         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15342             SendToICS(ics_prefix);
15343             SendToICS("clearboard\n");
15344         } else {
15345             int nonEmpty = 0;
15346             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15347                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15348                 for (y = 0; y < BOARD_HEIGHT; y++) {
15349                     if (gameMode == IcsExamining) {
15350                         if (boards[currentMove][y][x] != EmptySquare) {
15351                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15352                                     AAA + x, ONE + y);
15353                             SendToICS(buf);
15354                         }
15355                     } else if(boards[0][y][x] != DarkSquare) {
15356                         if(boards[0][y][x] != p) nonEmpty++;
15357                         boards[0][y][x] = p;
15358                     }
15359                 }
15360             }
15361             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15362                 int r;
15363                 for(r = 0; r < BOARD_HEIGHT; r++) {
15364                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15365                     ChessSquare p = menuBoard[r][x];
15366                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15367                   }
15368                 }
15369                 DisplayMessage("Clicking clock again restores position", "");
15370                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15371                 if(!nonEmpty) { // asked to clear an empty board
15372                     CopyBoard(boards[0], menuBoard);
15373                 } else
15374                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15375                     CopyBoard(boards[0], initialPosition);
15376                 } else
15377                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15378                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15379                     CopyBoard(boards[0], erasedBoard);
15380                 } else
15381                     CopyBoard(erasedBoard, currentBoard);
15382
15383             }
15384         }
15385         if (gameMode == EditPosition) {
15386             DrawPosition(FALSE, boards[0]);
15387         }
15388         break;
15389
15390       case WhitePlay:
15391         SetWhiteToPlayEvent();
15392         break;
15393
15394       case BlackPlay:
15395         SetBlackToPlayEvent();
15396         break;
15397
15398       case EmptySquare:
15399         if (gameMode == IcsExamining) {
15400             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15401             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15402             SendToICS(buf);
15403         } else {
15404             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15405                 if(x == BOARD_LEFT-2) {
15406                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15407                     boards[0][y][1] = 0;
15408                 } else
15409                 if(x == BOARD_RGHT+1) {
15410                     if(y >= gameInfo.holdingsSize) break;
15411                     boards[0][y][BOARD_WIDTH-2] = 0;
15412                 } else break;
15413             }
15414             boards[0][y][x] = EmptySquare;
15415             DrawPosition(FALSE, boards[0]);
15416         }
15417         break;
15418
15419       case PromotePiece:
15420         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15421            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15422             selection = (ChessSquare) (PROMOTED(piece));
15423         } else if(piece == EmptySquare) selection = WhiteSilver;
15424         else selection = (ChessSquare)((int)piece - 1);
15425         goto defaultlabel;
15426
15427       case DemotePiece:
15428         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15429            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15430             selection = (ChessSquare) (DEMOTED(piece));
15431         } else if(piece == EmptySquare) selection = BlackSilver;
15432         else selection = (ChessSquare)((int)piece + 1);
15433         goto defaultlabel;
15434
15435       case WhiteQueen:
15436       case BlackQueen:
15437         if(gameInfo.variant == VariantShatranj ||
15438            gameInfo.variant == VariantXiangqi  ||
15439            gameInfo.variant == VariantCourier  ||
15440            gameInfo.variant == VariantASEAN    ||
15441            gameInfo.variant == VariantMakruk     )
15442             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15443         goto defaultlabel;
15444
15445       case WhiteKing:
15446       case BlackKing:
15447         if(gameInfo.variant == VariantXiangqi)
15448             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15449         if(gameInfo.variant == VariantKnightmate)
15450             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15451       default:
15452         defaultlabel:
15453         if (gameMode == IcsExamining) {
15454             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15455             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15456                      PieceToChar(selection), AAA + x, ONE + y);
15457             SendToICS(buf);
15458         } else {
15459             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15460                 int n;
15461                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15462                     n = PieceToNumber(selection - BlackPawn);
15463                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15464                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15465                     boards[0][BOARD_HEIGHT-1-n][1]++;
15466                 } else
15467                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15468                     n = PieceToNumber(selection);
15469                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15470                     boards[0][n][BOARD_WIDTH-1] = selection;
15471                     boards[0][n][BOARD_WIDTH-2]++;
15472                 }
15473             } else
15474             boards[0][y][x] = selection;
15475             DrawPosition(TRUE, boards[0]);
15476             ClearHighlights();
15477             fromX = fromY = -1;
15478         }
15479         break;
15480     }
15481 }
15482
15483
15484 void
15485 DropMenuEvent (ChessSquare selection, int x, int y)
15486 {
15487     ChessMove moveType;
15488
15489     switch (gameMode) {
15490       case IcsPlayingWhite:
15491       case MachinePlaysBlack:
15492         if (!WhiteOnMove(currentMove)) {
15493             DisplayMoveError(_("It is Black's turn"));
15494             return;
15495         }
15496         moveType = WhiteDrop;
15497         break;
15498       case IcsPlayingBlack:
15499       case MachinePlaysWhite:
15500         if (WhiteOnMove(currentMove)) {
15501             DisplayMoveError(_("It is White's turn"));
15502             return;
15503         }
15504         moveType = BlackDrop;
15505         break;
15506       case EditGame:
15507         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15508         break;
15509       default:
15510         return;
15511     }
15512
15513     if (moveType == BlackDrop && selection < BlackPawn) {
15514       selection = (ChessSquare) ((int) selection
15515                                  + (int) BlackPawn - (int) WhitePawn);
15516     }
15517     if (boards[currentMove][y][x] != EmptySquare) {
15518         DisplayMoveError(_("That square is occupied"));
15519         return;
15520     }
15521
15522     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15523 }
15524
15525 void
15526 AcceptEvent ()
15527 {
15528     /* Accept a pending offer of any kind from opponent */
15529
15530     if (appData.icsActive) {
15531         SendToICS(ics_prefix);
15532         SendToICS("accept\n");
15533     } else if (cmailMsgLoaded) {
15534         if (currentMove == cmailOldMove &&
15535             commentList[cmailOldMove] != NULL &&
15536             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15537                    "Black offers a draw" : "White offers a draw")) {
15538             TruncateGame();
15539             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15540             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15541         } else {
15542             DisplayError(_("There is no pending offer on this move"), 0);
15543             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15544         }
15545     } else {
15546         /* Not used for offers from chess program */
15547     }
15548 }
15549
15550 void
15551 DeclineEvent ()
15552 {
15553     /* Decline a pending offer of any kind from opponent */
15554
15555     if (appData.icsActive) {
15556         SendToICS(ics_prefix);
15557         SendToICS("decline\n");
15558     } else if (cmailMsgLoaded) {
15559         if (currentMove == cmailOldMove &&
15560             commentList[cmailOldMove] != NULL &&
15561             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15562                    "Black offers a draw" : "White offers a draw")) {
15563 #ifdef NOTDEF
15564             AppendComment(cmailOldMove, "Draw declined", TRUE);
15565             DisplayComment(cmailOldMove - 1, "Draw declined");
15566 #endif /*NOTDEF*/
15567         } else {
15568             DisplayError(_("There is no pending offer on this move"), 0);
15569         }
15570     } else {
15571         /* Not used for offers from chess program */
15572     }
15573 }
15574
15575 void
15576 RematchEvent ()
15577 {
15578     /* Issue ICS rematch command */
15579     if (appData.icsActive) {
15580         SendToICS(ics_prefix);
15581         SendToICS("rematch\n");
15582     }
15583 }
15584
15585 void
15586 CallFlagEvent ()
15587 {
15588     /* Call your opponent's flag (claim a win on time) */
15589     if (appData.icsActive) {
15590         SendToICS(ics_prefix);
15591         SendToICS("flag\n");
15592     } else {
15593         switch (gameMode) {
15594           default:
15595             return;
15596           case MachinePlaysWhite:
15597             if (whiteFlag) {
15598                 if (blackFlag)
15599                   GameEnds(GameIsDrawn, "Both players ran out of time",
15600                            GE_PLAYER);
15601                 else
15602                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15603             } else {
15604                 DisplayError(_("Your opponent is not out of time"), 0);
15605             }
15606             break;
15607           case MachinePlaysBlack:
15608             if (blackFlag) {
15609                 if (whiteFlag)
15610                   GameEnds(GameIsDrawn, "Both players ran out of time",
15611                            GE_PLAYER);
15612                 else
15613                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15614             } else {
15615                 DisplayError(_("Your opponent is not out of time"), 0);
15616             }
15617             break;
15618         }
15619     }
15620 }
15621
15622 void
15623 ClockClick (int which)
15624 {       // [HGM] code moved to back-end from winboard.c
15625         if(which) { // black clock
15626           if (gameMode == EditPosition || gameMode == IcsExamining) {
15627             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15628             SetBlackToPlayEvent();
15629           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15630                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15631           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15632           } else if (shiftKey) {
15633             AdjustClock(which, -1);
15634           } else if (gameMode == IcsPlayingWhite ||
15635                      gameMode == MachinePlaysBlack) {
15636             CallFlagEvent();
15637           }
15638         } else { // white clock
15639           if (gameMode == EditPosition || gameMode == IcsExamining) {
15640             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15641             SetWhiteToPlayEvent();
15642           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15643                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15644           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15645           } else if (shiftKey) {
15646             AdjustClock(which, -1);
15647           } else if (gameMode == IcsPlayingBlack ||
15648                    gameMode == MachinePlaysWhite) {
15649             CallFlagEvent();
15650           }
15651         }
15652 }
15653
15654 void
15655 DrawEvent ()
15656 {
15657     /* Offer draw or accept pending draw offer from opponent */
15658
15659     if (appData.icsActive) {
15660         /* Note: tournament rules require draw offers to be
15661            made after you make your move but before you punch
15662            your clock.  Currently ICS doesn't let you do that;
15663            instead, you immediately punch your clock after making
15664            a move, but you can offer a draw at any time. */
15665
15666         SendToICS(ics_prefix);
15667         SendToICS("draw\n");
15668         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15669     } else if (cmailMsgLoaded) {
15670         if (currentMove == cmailOldMove &&
15671             commentList[cmailOldMove] != NULL &&
15672             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15673                    "Black offers a draw" : "White offers a draw")) {
15674             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15675             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15676         } else if (currentMove == cmailOldMove + 1) {
15677             char *offer = WhiteOnMove(cmailOldMove) ?
15678               "White offers a draw" : "Black offers a draw";
15679             AppendComment(currentMove, offer, TRUE);
15680             DisplayComment(currentMove - 1, offer);
15681             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15682         } else {
15683             DisplayError(_("You must make your move before offering a draw"), 0);
15684             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15685         }
15686     } else if (first.offeredDraw) {
15687         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15688     } else {
15689         if (first.sendDrawOffers) {
15690             SendToProgram("draw\n", &first);
15691             userOfferedDraw = TRUE;
15692         }
15693     }
15694 }
15695
15696 void
15697 AdjournEvent ()
15698 {
15699     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15700
15701     if (appData.icsActive) {
15702         SendToICS(ics_prefix);
15703         SendToICS("adjourn\n");
15704     } else {
15705         /* Currently GNU Chess doesn't offer or accept Adjourns */
15706     }
15707 }
15708
15709
15710 void
15711 AbortEvent ()
15712 {
15713     /* Offer Abort or accept pending Abort offer from opponent */
15714
15715     if (appData.icsActive) {
15716         SendToICS(ics_prefix);
15717         SendToICS("abort\n");
15718     } else {
15719         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15720     }
15721 }
15722
15723 void
15724 ResignEvent ()
15725 {
15726     /* Resign.  You can do this even if it's not your turn. */
15727
15728     if (appData.icsActive) {
15729         SendToICS(ics_prefix);
15730         SendToICS("resign\n");
15731     } else {
15732         switch (gameMode) {
15733           case MachinePlaysWhite:
15734             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15735             break;
15736           case MachinePlaysBlack:
15737             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15738             break;
15739           case EditGame:
15740             if (cmailMsgLoaded) {
15741                 TruncateGame();
15742                 if (WhiteOnMove(cmailOldMove)) {
15743                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15744                 } else {
15745                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15746                 }
15747                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15748             }
15749             break;
15750           default:
15751             break;
15752         }
15753     }
15754 }
15755
15756
15757 void
15758 StopObservingEvent ()
15759 {
15760     /* Stop observing current games */
15761     SendToICS(ics_prefix);
15762     SendToICS("unobserve\n");
15763 }
15764
15765 void
15766 StopExaminingEvent ()
15767 {
15768     /* Stop observing current game */
15769     SendToICS(ics_prefix);
15770     SendToICS("unexamine\n");
15771 }
15772
15773 void
15774 ForwardInner (int target)
15775 {
15776     int limit; int oldSeekGraphUp = seekGraphUp;
15777
15778     if (appData.debugMode)
15779         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15780                 target, currentMove, forwardMostMove);
15781
15782     if (gameMode == EditPosition)
15783       return;
15784
15785     seekGraphUp = FALSE;
15786     MarkTargetSquares(1);
15787     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15788
15789     if (gameMode == PlayFromGameFile && !pausing)
15790       PauseEvent();
15791
15792     if (gameMode == IcsExamining && pausing)
15793       limit = pauseExamForwardMostMove;
15794     else
15795       limit = forwardMostMove;
15796
15797     if (target > limit) target = limit;
15798
15799     if (target > 0 && moveList[target - 1][0]) {
15800         int fromX, fromY, toX, toY;
15801         toX = moveList[target - 1][2] - AAA;
15802         toY = moveList[target - 1][3] - ONE;
15803         if (moveList[target - 1][1] == '@') {
15804             if (appData.highlightLastMove) {
15805                 SetHighlights(-1, -1, toX, toY);
15806             }
15807         } else {
15808             int viaX = moveList[target - 1][5] - AAA;
15809             int viaY = moveList[target - 1][6] - ONE;
15810             fromX = moveList[target - 1][0] - AAA;
15811             fromY = moveList[target - 1][1] - ONE;
15812             if (target == currentMove + 1) {
15813                 if(moveList[target - 1][4] == ';') { // multi-leg
15814                     ChessSquare piece = boards[currentMove][viaY][viaX];
15815                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15816                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15817                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15818                     boards[currentMove][viaY][viaX] = piece;
15819                 } else
15820                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15821             }
15822             if (appData.highlightLastMove) {
15823                 SetHighlights(fromX, fromY, toX, toY);
15824             }
15825         }
15826     }
15827     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15828         gameMode == Training || gameMode == PlayFromGameFile ||
15829         gameMode == AnalyzeFile) {
15830         while (currentMove < target) {
15831             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15832             SendMoveToProgram(currentMove++, &first);
15833         }
15834     } else {
15835         currentMove = target;
15836     }
15837
15838     if (gameMode == EditGame || gameMode == EndOfGame) {
15839         whiteTimeRemaining = timeRemaining[0][currentMove];
15840         blackTimeRemaining = timeRemaining[1][currentMove];
15841     }
15842     DisplayBothClocks();
15843     DisplayMove(currentMove - 1);
15844     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15845     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15846     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15847         DisplayComment(currentMove - 1, commentList[currentMove]);
15848     }
15849     ClearMap(); // [HGM] exclude: invalidate map
15850 }
15851
15852
15853 void
15854 ForwardEvent ()
15855 {
15856     if (gameMode == IcsExamining && !pausing) {
15857         SendToICS(ics_prefix);
15858         SendToICS("forward\n");
15859     } else {
15860         ForwardInner(currentMove + 1);
15861     }
15862 }
15863
15864 void
15865 ToEndEvent ()
15866 {
15867     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15868         /* to optimze, we temporarily turn off analysis mode while we feed
15869          * the remaining moves to the engine. Otherwise we get analysis output
15870          * after each move.
15871          */
15872         if (first.analysisSupport) {
15873           SendToProgram("exit\nforce\n", &first);
15874           first.analyzing = FALSE;
15875         }
15876     }
15877
15878     if (gameMode == IcsExamining && !pausing) {
15879         SendToICS(ics_prefix);
15880         SendToICS("forward 999999\n");
15881     } else {
15882         ForwardInner(forwardMostMove);
15883     }
15884
15885     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15886         /* we have fed all the moves, so reactivate analysis mode */
15887         SendToProgram("analyze\n", &first);
15888         first.analyzing = TRUE;
15889         /*first.maybeThinking = TRUE;*/
15890         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15891     }
15892 }
15893
15894 void
15895 BackwardInner (int target)
15896 {
15897     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15898
15899     if (appData.debugMode)
15900         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15901                 target, currentMove, forwardMostMove);
15902
15903     if (gameMode == EditPosition) return;
15904     seekGraphUp = FALSE;
15905     MarkTargetSquares(1);
15906     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15907     if (currentMove <= backwardMostMove) {
15908         ClearHighlights();
15909         DrawPosition(full_redraw, boards[currentMove]);
15910         return;
15911     }
15912     if (gameMode == PlayFromGameFile && !pausing)
15913       PauseEvent();
15914
15915     if (moveList[target][0]) {
15916         int fromX, fromY, toX, toY;
15917         toX = moveList[target][2] - AAA;
15918         toY = moveList[target][3] - ONE;
15919         if (moveList[target][1] == '@') {
15920             if (appData.highlightLastMove) {
15921                 SetHighlights(-1, -1, toX, toY);
15922             }
15923         } else {
15924             fromX = moveList[target][0] - AAA;
15925             fromY = moveList[target][1] - ONE;
15926             if (target == currentMove - 1) {
15927                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15928             }
15929             if (appData.highlightLastMove) {
15930                 SetHighlights(fromX, fromY, toX, toY);
15931             }
15932         }
15933     }
15934     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15935         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15936         while (currentMove > target) {
15937             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15938                 // null move cannot be undone. Reload program with move history before it.
15939                 int i;
15940                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15941                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15942                 }
15943                 SendBoard(&first, i);
15944               if(second.analyzing) SendBoard(&second, i);
15945                 for(currentMove=i; currentMove<target; currentMove++) {
15946                     SendMoveToProgram(currentMove, &first);
15947                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15948                 }
15949                 break;
15950             }
15951             SendToBoth("undo\n");
15952             currentMove--;
15953         }
15954     } else {
15955         currentMove = target;
15956     }
15957
15958     if (gameMode == EditGame || gameMode == EndOfGame) {
15959         whiteTimeRemaining = timeRemaining[0][currentMove];
15960         blackTimeRemaining = timeRemaining[1][currentMove];
15961     }
15962     DisplayBothClocks();
15963     DisplayMove(currentMove - 1);
15964     DrawPosition(full_redraw, boards[currentMove]);
15965     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15966     // [HGM] PV info: routine tests if comment empty
15967     DisplayComment(currentMove - 1, commentList[currentMove]);
15968     ClearMap(); // [HGM] exclude: invalidate map
15969 }
15970
15971 void
15972 BackwardEvent ()
15973 {
15974     if (gameMode == IcsExamining && !pausing) {
15975         SendToICS(ics_prefix);
15976         SendToICS("backward\n");
15977     } else {
15978         BackwardInner(currentMove - 1);
15979     }
15980 }
15981
15982 void
15983 ToStartEvent ()
15984 {
15985     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15986         /* to optimize, we temporarily turn off analysis mode while we undo
15987          * all the moves. Otherwise we get analysis output after each undo.
15988          */
15989         if (first.analysisSupport) {
15990           SendToProgram("exit\nforce\n", &first);
15991           first.analyzing = FALSE;
15992         }
15993     }
15994
15995     if (gameMode == IcsExamining && !pausing) {
15996         SendToICS(ics_prefix);
15997         SendToICS("backward 999999\n");
15998     } else {
15999         BackwardInner(backwardMostMove);
16000     }
16001
16002     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16003         /* we have fed all the moves, so reactivate analysis mode */
16004         SendToProgram("analyze\n", &first);
16005         first.analyzing = TRUE;
16006         /*first.maybeThinking = TRUE;*/
16007         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16008     }
16009 }
16010
16011 void
16012 ToNrEvent (int to)
16013 {
16014   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16015   if (to >= forwardMostMove) to = forwardMostMove;
16016   if (to <= backwardMostMove) to = backwardMostMove;
16017   if (to < currentMove) {
16018     BackwardInner(to);
16019   } else {
16020     ForwardInner(to);
16021   }
16022 }
16023
16024 void
16025 RevertEvent (Boolean annotate)
16026 {
16027     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16028         return;
16029     }
16030     if (gameMode != IcsExamining) {
16031         DisplayError(_("You are not examining a game"), 0);
16032         return;
16033     }
16034     if (pausing) {
16035         DisplayError(_("You can't revert while pausing"), 0);
16036         return;
16037     }
16038     SendToICS(ics_prefix);
16039     SendToICS("revert\n");
16040 }
16041
16042 void
16043 RetractMoveEvent ()
16044 {
16045     switch (gameMode) {
16046       case MachinePlaysWhite:
16047       case MachinePlaysBlack:
16048         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16049             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16050             return;
16051         }
16052         if (forwardMostMove < 2) return;
16053         currentMove = forwardMostMove = forwardMostMove - 2;
16054         whiteTimeRemaining = timeRemaining[0][currentMove];
16055         blackTimeRemaining = timeRemaining[1][currentMove];
16056         DisplayBothClocks();
16057         DisplayMove(currentMove - 1);
16058         ClearHighlights();/*!! could figure this out*/
16059         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16060         SendToProgram("remove\n", &first);
16061         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16062         break;
16063
16064       case BeginningOfGame:
16065       default:
16066         break;
16067
16068       case IcsPlayingWhite:
16069       case IcsPlayingBlack:
16070         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16071             SendToICS(ics_prefix);
16072             SendToICS("takeback 2\n");
16073         } else {
16074             SendToICS(ics_prefix);
16075             SendToICS("takeback 1\n");
16076         }
16077         break;
16078     }
16079 }
16080
16081 void
16082 MoveNowEvent ()
16083 {
16084     ChessProgramState *cps;
16085
16086     switch (gameMode) {
16087       case MachinePlaysWhite:
16088         if (!WhiteOnMove(forwardMostMove)) {
16089             DisplayError(_("It is your turn"), 0);
16090             return;
16091         }
16092         cps = &first;
16093         break;
16094       case MachinePlaysBlack:
16095         if (WhiteOnMove(forwardMostMove)) {
16096             DisplayError(_("It is your turn"), 0);
16097             return;
16098         }
16099         cps = &first;
16100         break;
16101       case TwoMachinesPlay:
16102         if (WhiteOnMove(forwardMostMove) ==
16103             (first.twoMachinesColor[0] == 'w')) {
16104             cps = &first;
16105         } else {
16106             cps = &second;
16107         }
16108         break;
16109       case BeginningOfGame:
16110       default:
16111         return;
16112     }
16113     SendToProgram("?\n", cps);
16114 }
16115
16116 void
16117 TruncateGameEvent ()
16118 {
16119     EditGameEvent();
16120     if (gameMode != EditGame) return;
16121     TruncateGame();
16122 }
16123
16124 void
16125 TruncateGame ()
16126 {
16127     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16128     if (forwardMostMove > currentMove) {
16129         if (gameInfo.resultDetails != NULL) {
16130             free(gameInfo.resultDetails);
16131             gameInfo.resultDetails = NULL;
16132             gameInfo.result = GameUnfinished;
16133         }
16134         forwardMostMove = currentMove;
16135         HistorySet(parseList, backwardMostMove, forwardMostMove,
16136                    currentMove-1);
16137     }
16138 }
16139
16140 void
16141 HintEvent ()
16142 {
16143     if (appData.noChessProgram) return;
16144     switch (gameMode) {
16145       case MachinePlaysWhite:
16146         if (WhiteOnMove(forwardMostMove)) {
16147             DisplayError(_("Wait until your turn."), 0);
16148             return;
16149         }
16150         break;
16151       case BeginningOfGame:
16152       case MachinePlaysBlack:
16153         if (!WhiteOnMove(forwardMostMove)) {
16154             DisplayError(_("Wait until your turn."), 0);
16155             return;
16156         }
16157         break;
16158       default:
16159         DisplayError(_("No hint available"), 0);
16160         return;
16161     }
16162     SendToProgram("hint\n", &first);
16163     hintRequested = TRUE;
16164 }
16165
16166 int
16167 SaveSelected (FILE *g, int dummy, char *dummy2)
16168 {
16169     ListGame * lg = (ListGame *) gameList.head;
16170     int nItem, cnt=0;
16171     FILE *f;
16172
16173     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16174         DisplayError(_("Game list not loaded or empty"), 0);
16175         return 0;
16176     }
16177
16178     creatingBook = TRUE; // suppresses stuff during load game
16179
16180     /* Get list size */
16181     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16182         if(lg->position >= 0) { // selected?
16183             LoadGame(f, nItem, "", TRUE);
16184             SaveGamePGN2(g); // leaves g open
16185             cnt++; DoEvents();
16186         }
16187         lg = (ListGame *) lg->node.succ;
16188     }
16189
16190     fclose(g);
16191     creatingBook = FALSE;
16192
16193     return cnt;
16194 }
16195
16196 void
16197 CreateBookEvent ()
16198 {
16199     ListGame * lg = (ListGame *) gameList.head;
16200     FILE *f, *g;
16201     int nItem;
16202     static int secondTime = FALSE;
16203
16204     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16205         DisplayError(_("Game list not loaded or empty"), 0);
16206         return;
16207     }
16208
16209     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16210         fclose(g);
16211         secondTime++;
16212         DisplayNote(_("Book file exists! Try again for overwrite."));
16213         return;
16214     }
16215
16216     creatingBook = TRUE;
16217     secondTime = FALSE;
16218
16219     /* Get list size */
16220     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16221         if(lg->position >= 0) {
16222             LoadGame(f, nItem, "", TRUE);
16223             AddGameToBook(TRUE);
16224             DoEvents();
16225         }
16226         lg = (ListGame *) lg->node.succ;
16227     }
16228
16229     creatingBook = FALSE;
16230     FlushBook();
16231 }
16232
16233 void
16234 BookEvent ()
16235 {
16236     if (appData.noChessProgram) return;
16237     switch (gameMode) {
16238       case MachinePlaysWhite:
16239         if (WhiteOnMove(forwardMostMove)) {
16240             DisplayError(_("Wait until your turn."), 0);
16241             return;
16242         }
16243         break;
16244       case BeginningOfGame:
16245       case MachinePlaysBlack:
16246         if (!WhiteOnMove(forwardMostMove)) {
16247             DisplayError(_("Wait until your turn."), 0);
16248             return;
16249         }
16250         break;
16251       case EditPosition:
16252         EditPositionDone(TRUE);
16253         break;
16254       case TwoMachinesPlay:
16255         return;
16256       default:
16257         break;
16258     }
16259     SendToProgram("bk\n", &first);
16260     bookOutput[0] = NULLCHAR;
16261     bookRequested = TRUE;
16262 }
16263
16264 void
16265 AboutGameEvent ()
16266 {
16267     char *tags = PGNTags(&gameInfo);
16268     TagsPopUp(tags, CmailMsg());
16269     free(tags);
16270 }
16271
16272 /* end button procedures */
16273
16274 void
16275 PrintPosition (FILE *fp, int move)
16276 {
16277     int i, j;
16278
16279     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16280         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16281             char c = PieceToChar(boards[move][i][j]);
16282             fputc(c == '?' ? '.' : c, fp);
16283             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16284         }
16285     }
16286     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16287       fprintf(fp, "white to play\n");
16288     else
16289       fprintf(fp, "black to play\n");
16290 }
16291
16292 void
16293 PrintOpponents (FILE *fp)
16294 {
16295     if (gameInfo.white != NULL) {
16296         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16297     } else {
16298         fprintf(fp, "\n");
16299     }
16300 }
16301
16302 /* Find last component of program's own name, using some heuristics */
16303 void
16304 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16305 {
16306     char *p, *q, c;
16307     int local = (strcmp(host, "localhost") == 0);
16308     while (!local && (p = strchr(prog, ';')) != NULL) {
16309         p++;
16310         while (*p == ' ') p++;
16311         prog = p;
16312     }
16313     if (*prog == '"' || *prog == '\'') {
16314         q = strchr(prog + 1, *prog);
16315     } else {
16316         q = strchr(prog, ' ');
16317     }
16318     if (q == NULL) q = prog + strlen(prog);
16319     p = q;
16320     while (p >= prog && *p != '/' && *p != '\\') p--;
16321     p++;
16322     if(p == prog && *p == '"') p++;
16323     c = *q; *q = 0;
16324     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16325     memcpy(buf, p, q - p);
16326     buf[q - p] = NULLCHAR;
16327     if (!local) {
16328         strcat(buf, "@");
16329         strcat(buf, host);
16330     }
16331 }
16332
16333 char *
16334 TimeControlTagValue ()
16335 {
16336     char buf[MSG_SIZ];
16337     if (!appData.clockMode) {
16338       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16339     } else if (movesPerSession > 0) {
16340       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16341     } else if (timeIncrement == 0) {
16342       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16343     } else {
16344       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16345     }
16346     return StrSave(buf);
16347 }
16348
16349 void
16350 SetGameInfo ()
16351 {
16352     /* This routine is used only for certain modes */
16353     VariantClass v = gameInfo.variant;
16354     ChessMove r = GameUnfinished;
16355     char *p = NULL;
16356
16357     if(keepInfo) return;
16358
16359     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16360         r = gameInfo.result;
16361         p = gameInfo.resultDetails;
16362         gameInfo.resultDetails = NULL;
16363     }
16364     ClearGameInfo(&gameInfo);
16365     gameInfo.variant = v;
16366
16367     switch (gameMode) {
16368       case MachinePlaysWhite:
16369         gameInfo.event = StrSave( appData.pgnEventHeader );
16370         gameInfo.site = StrSave(HostName());
16371         gameInfo.date = PGNDate();
16372         gameInfo.round = StrSave("-");
16373         gameInfo.white = StrSave(first.tidy);
16374         gameInfo.black = StrSave(UserName());
16375         gameInfo.timeControl = TimeControlTagValue();
16376         break;
16377
16378       case MachinePlaysBlack:
16379         gameInfo.event = StrSave( appData.pgnEventHeader );
16380         gameInfo.site = StrSave(HostName());
16381         gameInfo.date = PGNDate();
16382         gameInfo.round = StrSave("-");
16383         gameInfo.white = StrSave(UserName());
16384         gameInfo.black = StrSave(first.tidy);
16385         gameInfo.timeControl = TimeControlTagValue();
16386         break;
16387
16388       case TwoMachinesPlay:
16389         gameInfo.event = StrSave( appData.pgnEventHeader );
16390         gameInfo.site = StrSave(HostName());
16391         gameInfo.date = PGNDate();
16392         if (roundNr > 0) {
16393             char buf[MSG_SIZ];
16394             snprintf(buf, MSG_SIZ, "%d", roundNr);
16395             gameInfo.round = StrSave(buf);
16396         } else {
16397             gameInfo.round = StrSave("-");
16398         }
16399         if (first.twoMachinesColor[0] == 'w') {
16400             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16401             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16402         } else {
16403             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16404             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16405         }
16406         gameInfo.timeControl = TimeControlTagValue();
16407         break;
16408
16409       case EditGame:
16410         gameInfo.event = StrSave("Edited game");
16411         gameInfo.site = StrSave(HostName());
16412         gameInfo.date = PGNDate();
16413         gameInfo.round = StrSave("-");
16414         gameInfo.white = StrSave("-");
16415         gameInfo.black = StrSave("-");
16416         gameInfo.result = r;
16417         gameInfo.resultDetails = p;
16418         break;
16419
16420       case EditPosition:
16421         gameInfo.event = StrSave("Edited position");
16422         gameInfo.site = StrSave(HostName());
16423         gameInfo.date = PGNDate();
16424         gameInfo.round = StrSave("-");
16425         gameInfo.white = StrSave("-");
16426         gameInfo.black = StrSave("-");
16427         break;
16428
16429       case IcsPlayingWhite:
16430       case IcsPlayingBlack:
16431       case IcsObserving:
16432       case IcsExamining:
16433         break;
16434
16435       case PlayFromGameFile:
16436         gameInfo.event = StrSave("Game from non-PGN file");
16437         gameInfo.site = StrSave(HostName());
16438         gameInfo.date = PGNDate();
16439         gameInfo.round = StrSave("-");
16440         gameInfo.white = StrSave("?");
16441         gameInfo.black = StrSave("?");
16442         break;
16443
16444       default:
16445         break;
16446     }
16447 }
16448
16449 void
16450 ReplaceComment (int index, char *text)
16451 {
16452     int len;
16453     char *p;
16454     float score;
16455
16456     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16457        pvInfoList[index-1].depth == len &&
16458        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16459        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16460     while (*text == '\n') text++;
16461     len = strlen(text);
16462     while (len > 0 && text[len - 1] == '\n') len--;
16463
16464     if (commentList[index] != NULL)
16465       free(commentList[index]);
16466
16467     if (len == 0) {
16468         commentList[index] = NULL;
16469         return;
16470     }
16471   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16472       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16473       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16474     commentList[index] = (char *) malloc(len + 2);
16475     strncpy(commentList[index], text, len);
16476     commentList[index][len] = '\n';
16477     commentList[index][len + 1] = NULLCHAR;
16478   } else {
16479     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16480     char *p;
16481     commentList[index] = (char *) malloc(len + 7);
16482     safeStrCpy(commentList[index], "{\n", 3);
16483     safeStrCpy(commentList[index]+2, text, len+1);
16484     commentList[index][len+2] = NULLCHAR;
16485     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16486     strcat(commentList[index], "\n}\n");
16487   }
16488 }
16489
16490 void
16491 CrushCRs (char *text)
16492 {
16493   char *p = text;
16494   char *q = text;
16495   char ch;
16496
16497   do {
16498     ch = *p++;
16499     if (ch == '\r') continue;
16500     *q++ = ch;
16501   } while (ch != '\0');
16502 }
16503
16504 void
16505 AppendComment (int index, char *text, Boolean addBraces)
16506 /* addBraces  tells if we should add {} */
16507 {
16508     int oldlen, len;
16509     char *old;
16510
16511 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16512     if(addBraces == 3) addBraces = 0; else // force appending literally
16513     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16514
16515     CrushCRs(text);
16516     while (*text == '\n') text++;
16517     len = strlen(text);
16518     while (len > 0 && text[len - 1] == '\n') len--;
16519     text[len] = NULLCHAR;
16520
16521     if (len == 0) return;
16522
16523     if (commentList[index] != NULL) {
16524       Boolean addClosingBrace = addBraces;
16525         old = commentList[index];
16526         oldlen = strlen(old);
16527         while(commentList[index][oldlen-1] ==  '\n')
16528           commentList[index][--oldlen] = NULLCHAR;
16529         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16530         safeStrCpy(commentList[index], old, oldlen + len + 6);
16531         free(old);
16532         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16533         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16534           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16535           while (*text == '\n') { text++; len--; }
16536           commentList[index][--oldlen] = NULLCHAR;
16537       }
16538         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16539         else          strcat(commentList[index], "\n");
16540         strcat(commentList[index], text);
16541         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16542         else          strcat(commentList[index], "\n");
16543     } else {
16544         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16545         if(addBraces)
16546           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16547         else commentList[index][0] = NULLCHAR;
16548         strcat(commentList[index], text);
16549         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16550         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16551     }
16552 }
16553
16554 static char *
16555 FindStr (char * text, char * sub_text)
16556 {
16557     char * result = strstr( text, sub_text );
16558
16559     if( result != NULL ) {
16560         result += strlen( sub_text );
16561     }
16562
16563     return result;
16564 }
16565
16566 /* [AS] Try to extract PV info from PGN comment */
16567 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16568 char *
16569 GetInfoFromComment (int index, char * text)
16570 {
16571     char * sep = text, *p;
16572
16573     if( text != NULL && index > 0 ) {
16574         int score = 0;
16575         int depth = 0;
16576         int time = -1, sec = 0, deci;
16577         char * s_eval = FindStr( text, "[%eval " );
16578         char * s_emt = FindStr( text, "[%emt " );
16579 #if 0
16580         if( s_eval != NULL || s_emt != NULL ) {
16581 #else
16582         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16583 #endif
16584             /* New style */
16585             char delim;
16586
16587             if( s_eval != NULL ) {
16588                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16589                     return text;
16590                 }
16591
16592                 if( delim != ']' ) {
16593                     return text;
16594                 }
16595             }
16596
16597             if( s_emt != NULL ) {
16598             }
16599                 return text;
16600         }
16601         else {
16602             /* We expect something like: [+|-]nnn.nn/dd */
16603             int score_lo = 0;
16604
16605             if(*text != '{') return text; // [HGM] braces: must be normal comment
16606
16607             sep = strchr( text, '/' );
16608             if( sep == NULL || sep < (text+4) ) {
16609                 return text;
16610             }
16611
16612             p = text;
16613             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16614             if(p[1] == '(') { // comment starts with PV
16615                p = strchr(p, ')'); // locate end of PV
16616                if(p == NULL || sep < p+5) return text;
16617                // at this point we have something like "{(.*) +0.23/6 ..."
16618                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16619                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16620                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16621             }
16622             time = -1; sec = -1; deci = -1;
16623             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16624                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16625                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16626                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16627                 return text;
16628             }
16629
16630             if( score_lo < 0 || score_lo >= 100 ) {
16631                 return text;
16632             }
16633
16634             if(sec >= 0) time = 600*time + 10*sec; else
16635             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16636
16637             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16638
16639             /* [HGM] PV time: now locate end of PV info */
16640             while( *++sep >= '0' && *sep <= '9'); // strip depth
16641             if(time >= 0)
16642             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16643             if(sec >= 0)
16644             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16645             if(deci >= 0)
16646             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16647             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16648         }
16649
16650         if( depth <= 0 ) {
16651             return text;
16652         }
16653
16654         if( time < 0 ) {
16655             time = -1;
16656         }
16657
16658         pvInfoList[index-1].depth = depth;
16659         pvInfoList[index-1].score = score;
16660         pvInfoList[index-1].time  = 10*time; // centi-sec
16661         if(*sep == '}') *sep = 0; else *--sep = '{';
16662         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16663     }
16664     return sep;
16665 }
16666
16667 void
16668 SendToProgram (char *message, ChessProgramState *cps)
16669 {
16670     int count, outCount, error;
16671     char buf[MSG_SIZ];
16672
16673     if (cps->pr == NoProc) return;
16674     Attention(cps);
16675
16676     if (appData.debugMode) {
16677         TimeMark now;
16678         GetTimeMark(&now);
16679         fprintf(debugFP, "%ld >%-6s: %s",
16680                 SubtractTimeMarks(&now, &programStartTime),
16681                 cps->which, message);
16682         if(serverFP)
16683             fprintf(serverFP, "%ld >%-6s: %s",
16684                 SubtractTimeMarks(&now, &programStartTime),
16685                 cps->which, message), fflush(serverFP);
16686     }
16687
16688     count = strlen(message);
16689     outCount = OutputToProcess(cps->pr, message, count, &error);
16690     if (outCount < count && !exiting
16691                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16692       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16693       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16694         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16695             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16696                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16697                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16698                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16699             } else {
16700                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16701                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16702                 gameInfo.result = res;
16703             }
16704             gameInfo.resultDetails = StrSave(buf);
16705         }
16706         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16707         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16708     }
16709 }
16710
16711 void
16712 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16713 {
16714     char *end_str;
16715     char buf[MSG_SIZ];
16716     ChessProgramState *cps = (ChessProgramState *)closure;
16717
16718     if (isr != cps->isr) return; /* Killed intentionally */
16719     if (count <= 0) {
16720         if (count == 0) {
16721             RemoveInputSource(cps->isr);
16722             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16723                     _(cps->which), cps->program);
16724             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16725             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16726                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16727                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16728                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16729                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16730                 } else {
16731                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16732                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16733                     gameInfo.result = res;
16734                 }
16735                 gameInfo.resultDetails = StrSave(buf);
16736             }
16737             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16738             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16739         } else {
16740             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16741                     _(cps->which), cps->program);
16742             RemoveInputSource(cps->isr);
16743
16744             /* [AS] Program is misbehaving badly... kill it */
16745             if( count == -2 ) {
16746                 DestroyChildProcess( cps->pr, 9 );
16747                 cps->pr = NoProc;
16748             }
16749
16750             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16751         }
16752         return;
16753     }
16754
16755     if ((end_str = strchr(message, '\r')) != NULL)
16756       *end_str = NULLCHAR;
16757     if ((end_str = strchr(message, '\n')) != NULL)
16758       *end_str = NULLCHAR;
16759
16760     if (appData.debugMode) {
16761         TimeMark now; int print = 1;
16762         char *quote = ""; char c; int i;
16763
16764         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16765                 char start = message[0];
16766                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16767                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16768                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16769                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16770                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16771                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16772                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16773                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16774                    sscanf(message, "hint: %c", &c)!=1 &&
16775                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16776                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16777                     print = (appData.engineComments >= 2);
16778                 }
16779                 message[0] = start; // restore original message
16780         }
16781         if(print) {
16782                 GetTimeMark(&now);
16783                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16784                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16785                         quote,
16786                         message);
16787                 if(serverFP)
16788                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16789                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16790                         quote,
16791                         message), fflush(serverFP);
16792         }
16793     }
16794
16795     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16796     if (appData.icsEngineAnalyze) {
16797         if (strstr(message, "whisper") != NULL ||
16798              strstr(message, "kibitz") != NULL ||
16799             strstr(message, "tellics") != NULL) return;
16800     }
16801
16802     HandleMachineMove(message, cps);
16803 }
16804
16805
16806 void
16807 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16808 {
16809     char buf[MSG_SIZ];
16810     int seconds;
16811
16812     if( timeControl_2 > 0 ) {
16813         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16814             tc = timeControl_2;
16815         }
16816     }
16817     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16818     inc /= cps->timeOdds;
16819     st  /= cps->timeOdds;
16820
16821     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16822
16823     if (st > 0) {
16824       /* Set exact time per move, normally using st command */
16825       if (cps->stKludge) {
16826         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16827         seconds = st % 60;
16828         if (seconds == 0) {
16829           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16830         } else {
16831           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16832         }
16833       } else {
16834         snprintf(buf, MSG_SIZ, "st %d\n", st);
16835       }
16836     } else {
16837       /* Set conventional or incremental time control, using level command */
16838       if (seconds == 0) {
16839         /* Note old gnuchess bug -- minutes:seconds used to not work.
16840            Fixed in later versions, but still avoid :seconds
16841            when seconds is 0. */
16842         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16843       } else {
16844         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16845                  seconds, inc/1000.);
16846       }
16847     }
16848     SendToProgram(buf, cps);
16849
16850     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16851     /* Orthogonally, limit search to given depth */
16852     if (sd > 0) {
16853       if (cps->sdKludge) {
16854         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16855       } else {
16856         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16857       }
16858       SendToProgram(buf, cps);
16859     }
16860
16861     if(cps->nps >= 0) { /* [HGM] nps */
16862         if(cps->supportsNPS == FALSE)
16863           cps->nps = -1; // don't use if engine explicitly says not supported!
16864         else {
16865           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16866           SendToProgram(buf, cps);
16867         }
16868     }
16869 }
16870
16871 ChessProgramState *
16872 WhitePlayer ()
16873 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16874 {
16875     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16876        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16877         return &second;
16878     return &first;
16879 }
16880
16881 void
16882 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16883 {
16884     char message[MSG_SIZ];
16885     long time, otime;
16886
16887     /* Note: this routine must be called when the clocks are stopped
16888        or when they have *just* been set or switched; otherwise
16889        it will be off by the time since the current tick started.
16890     */
16891     if (machineWhite) {
16892         time = whiteTimeRemaining / 10;
16893         otime = blackTimeRemaining / 10;
16894     } else {
16895         time = blackTimeRemaining / 10;
16896         otime = whiteTimeRemaining / 10;
16897     }
16898     /* [HGM] translate opponent's time by time-odds factor */
16899     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16900
16901     if (time <= 0) time = 1;
16902     if (otime <= 0) otime = 1;
16903
16904     snprintf(message, MSG_SIZ, "time %ld\n", time);
16905     SendToProgram(message, cps);
16906
16907     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16908     SendToProgram(message, cps);
16909 }
16910
16911 char *
16912 EngineDefinedVariant (ChessProgramState *cps, int n)
16913 {   // return name of n-th unknown variant that engine supports
16914     static char buf[MSG_SIZ];
16915     char *p, *s = cps->variants;
16916     if(!s) return NULL;
16917     do { // parse string from variants feature
16918       VariantClass v;
16919         p = strchr(s, ',');
16920         if(p) *p = NULLCHAR;
16921       v = StringToVariant(s);
16922       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16923         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16924             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16925                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16926                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16927                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16928             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16929         }
16930         if(p) *p++ = ',';
16931         if(n < 0) return buf;
16932     } while(s = p);
16933     return NULL;
16934 }
16935
16936 int
16937 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16938 {
16939   char buf[MSG_SIZ];
16940   int len = strlen(name);
16941   int val;
16942
16943   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16944     (*p) += len + 1;
16945     sscanf(*p, "%d", &val);
16946     *loc = (val != 0);
16947     while (**p && **p != ' ')
16948       (*p)++;
16949     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16950     SendToProgram(buf, cps);
16951     return TRUE;
16952   }
16953   return FALSE;
16954 }
16955
16956 int
16957 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16958 {
16959   char buf[MSG_SIZ];
16960   int len = strlen(name);
16961   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16962     (*p) += len + 1;
16963     sscanf(*p, "%d", loc);
16964     while (**p && **p != ' ') (*p)++;
16965     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16966     SendToProgram(buf, cps);
16967     return TRUE;
16968   }
16969   return FALSE;
16970 }
16971
16972 int
16973 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16974 {
16975   char buf[MSG_SIZ];
16976   int len = strlen(name);
16977   if (strncmp((*p), name, len) == 0
16978       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16979     (*p) += len + 2;
16980     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16981     sscanf(*p, "%[^\"]", *loc);
16982     while (**p && **p != '\"') (*p)++;
16983     if (**p == '\"') (*p)++;
16984     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16985     SendToProgram(buf, cps);
16986     return TRUE;
16987   }
16988   return FALSE;
16989 }
16990
16991 int
16992 ParseOption (Option *opt, ChessProgramState *cps)
16993 // [HGM] options: process the string that defines an engine option, and determine
16994 // name, type, default value, and allowed value range
16995 {
16996         char *p, *q, buf[MSG_SIZ];
16997         int n, min = (-1)<<31, max = 1<<31, def;
16998
16999         opt->target = &opt->value;   // OK for spin/slider and checkbox
17000         if(p = strstr(opt->name, " -spin ")) {
17001             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17002             if(max < min) max = min; // enforce consistency
17003             if(def < min) def = min;
17004             if(def > max) def = max;
17005             opt->value = def;
17006             opt->min = min;
17007             opt->max = max;
17008             opt->type = Spin;
17009         } else if((p = strstr(opt->name, " -slider "))) {
17010             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17011             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17012             if(max < min) max = min; // enforce consistency
17013             if(def < min) def = min;
17014             if(def > max) def = max;
17015             opt->value = def;
17016             opt->min = min;
17017             opt->max = max;
17018             opt->type = Spin; // Slider;
17019         } else if((p = strstr(opt->name, " -string "))) {
17020             opt->textValue = p+9;
17021             opt->type = TextBox;
17022             opt->target = &opt->textValue;
17023         } else if((p = strstr(opt->name, " -file "))) {
17024             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17025             opt->target = opt->textValue = p+7;
17026             opt->type = FileName; // FileName;
17027             opt->target = &opt->textValue;
17028         } else if((p = strstr(opt->name, " -path "))) {
17029             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17030             opt->target = opt->textValue = p+7;
17031             opt->type = PathName; // PathName;
17032             opt->target = &opt->textValue;
17033         } else if(p = strstr(opt->name, " -check ")) {
17034             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17035             opt->value = (def != 0);
17036             opt->type = CheckBox;
17037         } else if(p = strstr(opt->name, " -combo ")) {
17038             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17039             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17040             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17041             opt->value = n = 0;
17042             while(q = StrStr(q, " /// ")) {
17043                 n++; *q = 0;    // count choices, and null-terminate each of them
17044                 q += 5;
17045                 if(*q == '*') { // remember default, which is marked with * prefix
17046                     q++;
17047                     opt->value = n;
17048                 }
17049                 cps->comboList[cps->comboCnt++] = q;
17050             }
17051             cps->comboList[cps->comboCnt++] = NULL;
17052             opt->max = n + 1;
17053             opt->type = ComboBox;
17054         } else if(p = strstr(opt->name, " -button")) {
17055             opt->type = Button;
17056         } else if(p = strstr(opt->name, " -save")) {
17057             opt->type = SaveButton;
17058         } else return FALSE;
17059         *p = 0; // terminate option name
17060         // now look if the command-line options define a setting for this engine option.
17061         if(cps->optionSettings && cps->optionSettings[0])
17062             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17063         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17064           snprintf(buf, MSG_SIZ, "option %s", p);
17065                 if(p = strstr(buf, ",")) *p = 0;
17066                 if(q = strchr(buf, '=')) switch(opt->type) {
17067                     case ComboBox:
17068                         for(n=0; n<opt->max; n++)
17069                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17070                         break;
17071                     case TextBox:
17072                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17073                         break;
17074                     case Spin:
17075                     case CheckBox:
17076                         opt->value = atoi(q+1);
17077                     default:
17078                         break;
17079                 }
17080                 strcat(buf, "\n");
17081                 SendToProgram(buf, cps);
17082         }
17083         return TRUE;
17084 }
17085
17086 void
17087 FeatureDone (ChessProgramState *cps, int val)
17088 {
17089   DelayedEventCallback cb = GetDelayedEvent();
17090   if ((cb == InitBackEnd3 && cps == &first) ||
17091       (cb == SettingsMenuIfReady && cps == &second) ||
17092       (cb == LoadEngine) ||
17093       (cb == TwoMachinesEventIfReady)) {
17094     CancelDelayedEvent();
17095     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17096   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17097   cps->initDone = val;
17098   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17099 }
17100
17101 /* Parse feature command from engine */
17102 void
17103 ParseFeatures (char *args, ChessProgramState *cps)
17104 {
17105   char *p = args;
17106   char *q = NULL;
17107   int val;
17108   char buf[MSG_SIZ];
17109
17110   for (;;) {
17111     while (*p == ' ') p++;
17112     if (*p == NULLCHAR) return;
17113
17114     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17115     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17116     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17117     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17118     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17119     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17120     if (BoolFeature(&p, "reuse", &val, cps)) {
17121       /* Engine can disable reuse, but can't enable it if user said no */
17122       if (!val) cps->reuse = FALSE;
17123       continue;
17124     }
17125     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17126     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17127       if (gameMode == TwoMachinesPlay) {
17128         DisplayTwoMachinesTitle();
17129       } else {
17130         DisplayTitle("");
17131       }
17132       continue;
17133     }
17134     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17135     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17136     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17137     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17138     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17139     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17140     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17141     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17142     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17143     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17144     if (IntFeature(&p, "done", &val, cps)) {
17145       FeatureDone(cps, val);
17146       continue;
17147     }
17148     /* Added by Tord: */
17149     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17150     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17151     /* End of additions by Tord */
17152
17153     /* [HGM] added features: */
17154     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17155     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17156     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17157     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17158     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17159     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17160     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17161     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17162         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17163         FREE(cps->option[cps->nrOptions].name);
17164         cps->option[cps->nrOptions].name = q; q = NULL;
17165         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17166           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17167             SendToProgram(buf, cps);
17168             continue;
17169         }
17170         if(cps->nrOptions >= MAX_OPTIONS) {
17171             cps->nrOptions--;
17172             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17173             DisplayError(buf, 0);
17174         }
17175         continue;
17176     }
17177     /* End of additions by HGM */
17178
17179     /* unknown feature: complain and skip */
17180     q = p;
17181     while (*q && *q != '=') q++;
17182     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17183     SendToProgram(buf, cps);
17184     p = q;
17185     if (*p == '=') {
17186       p++;
17187       if (*p == '\"') {
17188         p++;
17189         while (*p && *p != '\"') p++;
17190         if (*p == '\"') p++;
17191       } else {
17192         while (*p && *p != ' ') p++;
17193       }
17194     }
17195   }
17196
17197 }
17198
17199 void
17200 PeriodicUpdatesEvent (int newState)
17201 {
17202     if (newState == appData.periodicUpdates)
17203       return;
17204
17205     appData.periodicUpdates=newState;
17206
17207     /* Display type changes, so update it now */
17208 //    DisplayAnalysis();
17209
17210     /* Get the ball rolling again... */
17211     if (newState) {
17212         AnalysisPeriodicEvent(1);
17213         StartAnalysisClock();
17214     }
17215 }
17216
17217 void
17218 PonderNextMoveEvent (int newState)
17219 {
17220     if (newState == appData.ponderNextMove) return;
17221     if (gameMode == EditPosition) EditPositionDone(TRUE);
17222     if (newState) {
17223         SendToProgram("hard\n", &first);
17224         if (gameMode == TwoMachinesPlay) {
17225             SendToProgram("hard\n", &second);
17226         }
17227     } else {
17228         SendToProgram("easy\n", &first);
17229         thinkOutput[0] = NULLCHAR;
17230         if (gameMode == TwoMachinesPlay) {
17231             SendToProgram("easy\n", &second);
17232         }
17233     }
17234     appData.ponderNextMove = newState;
17235 }
17236
17237 void
17238 NewSettingEvent (int option, int *feature, char *command, int value)
17239 {
17240     char buf[MSG_SIZ];
17241
17242     if (gameMode == EditPosition) EditPositionDone(TRUE);
17243     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17244     if(feature == NULL || *feature) SendToProgram(buf, &first);
17245     if (gameMode == TwoMachinesPlay) {
17246         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17247     }
17248 }
17249
17250 void
17251 ShowThinkingEvent ()
17252 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17253 {
17254     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17255     int newState = appData.showThinking
17256         // [HGM] thinking: other features now need thinking output as well
17257         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17258
17259     if (oldState == newState) return;
17260     oldState = newState;
17261     if (gameMode == EditPosition) EditPositionDone(TRUE);
17262     if (oldState) {
17263         SendToProgram("post\n", &first);
17264         if (gameMode == TwoMachinesPlay) {
17265             SendToProgram("post\n", &second);
17266         }
17267     } else {
17268         SendToProgram("nopost\n", &first);
17269         thinkOutput[0] = NULLCHAR;
17270         if (gameMode == TwoMachinesPlay) {
17271             SendToProgram("nopost\n", &second);
17272         }
17273     }
17274 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17275 }
17276
17277 void
17278 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17279 {
17280   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17281   if (pr == NoProc) return;
17282   AskQuestion(title, question, replyPrefix, pr);
17283 }
17284
17285 void
17286 TypeInEvent (char firstChar)
17287 {
17288     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17289         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17290         gameMode == AnalyzeMode || gameMode == EditGame ||
17291         gameMode == EditPosition || gameMode == IcsExamining ||
17292         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17293         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17294                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17295                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17296         gameMode == Training) PopUpMoveDialog(firstChar);
17297 }
17298
17299 void
17300 TypeInDoneEvent (char *move)
17301 {
17302         Board board;
17303         int n, fromX, fromY, toX, toY;
17304         char promoChar;
17305         ChessMove moveType;
17306
17307         // [HGM] FENedit
17308         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17309                 EditPositionPasteFEN(move);
17310                 return;
17311         }
17312         // [HGM] movenum: allow move number to be typed in any mode
17313         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17314           ToNrEvent(2*n-1);
17315           return;
17316         }
17317         // undocumented kludge: allow command-line option to be typed in!
17318         // (potentially fatal, and does not implement the effect of the option.)
17319         // should only be used for options that are values on which future decisions will be made,
17320         // and definitely not on options that would be used during initialization.
17321         if(strstr(move, "!!! -") == move) {
17322             ParseArgsFromString(move+4);
17323             return;
17324         }
17325
17326       if (gameMode != EditGame && currentMove != forwardMostMove &&
17327         gameMode != Training) {
17328         DisplayMoveError(_("Displayed move is not current"));
17329       } else {
17330         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17331           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17332         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17333         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17334           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17335           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17336         } else {
17337           DisplayMoveError(_("Could not parse move"));
17338         }
17339       }
17340 }
17341
17342 void
17343 DisplayMove (int moveNumber)
17344 {
17345     char message[MSG_SIZ];
17346     char res[MSG_SIZ];
17347     char cpThinkOutput[MSG_SIZ];
17348
17349     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17350
17351     if (moveNumber == forwardMostMove - 1 ||
17352         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17353
17354         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17355
17356         if (strchr(cpThinkOutput, '\n')) {
17357             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17358         }
17359     } else {
17360         *cpThinkOutput = NULLCHAR;
17361     }
17362
17363     /* [AS] Hide thinking from human user */
17364     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17365         *cpThinkOutput = NULLCHAR;
17366         if( thinkOutput[0] != NULLCHAR ) {
17367             int i;
17368
17369             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17370                 cpThinkOutput[i] = '.';
17371             }
17372             cpThinkOutput[i] = NULLCHAR;
17373             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17374         }
17375     }
17376
17377     if (moveNumber == forwardMostMove - 1 &&
17378         gameInfo.resultDetails != NULL) {
17379         if (gameInfo.resultDetails[0] == NULLCHAR) {
17380           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17381         } else {
17382           snprintf(res, MSG_SIZ, " {%s} %s",
17383                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17384         }
17385     } else {
17386         res[0] = NULLCHAR;
17387     }
17388
17389     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17390         DisplayMessage(res, cpThinkOutput);
17391     } else {
17392       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17393                 WhiteOnMove(moveNumber) ? " " : ".. ",
17394                 parseList[moveNumber], res);
17395         DisplayMessage(message, cpThinkOutput);
17396     }
17397 }
17398
17399 void
17400 DisplayComment (int moveNumber, char *text)
17401 {
17402     char title[MSG_SIZ];
17403
17404     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17405       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17406     } else {
17407       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17408               WhiteOnMove(moveNumber) ? " " : ".. ",
17409               parseList[moveNumber]);
17410     }
17411     if (text != NULL && (appData.autoDisplayComment || commentUp))
17412         CommentPopUp(title, text);
17413 }
17414
17415 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17416  * might be busy thinking or pondering.  It can be omitted if your
17417  * gnuchess is configured to stop thinking immediately on any user
17418  * input.  However, that gnuchess feature depends on the FIONREAD
17419  * ioctl, which does not work properly on some flavors of Unix.
17420  */
17421 void
17422 Attention (ChessProgramState *cps)
17423 {
17424 #if ATTENTION
17425     if (!cps->useSigint) return;
17426     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17427     switch (gameMode) {
17428       case MachinePlaysWhite:
17429       case MachinePlaysBlack:
17430       case TwoMachinesPlay:
17431       case IcsPlayingWhite:
17432       case IcsPlayingBlack:
17433       case AnalyzeMode:
17434       case AnalyzeFile:
17435         /* Skip if we know it isn't thinking */
17436         if (!cps->maybeThinking) return;
17437         if (appData.debugMode)
17438           fprintf(debugFP, "Interrupting %s\n", cps->which);
17439         InterruptChildProcess(cps->pr);
17440         cps->maybeThinking = FALSE;
17441         break;
17442       default:
17443         break;
17444     }
17445 #endif /*ATTENTION*/
17446 }
17447
17448 int
17449 CheckFlags ()
17450 {
17451     if (whiteTimeRemaining <= 0) {
17452         if (!whiteFlag) {
17453             whiteFlag = TRUE;
17454             if (appData.icsActive) {
17455                 if (appData.autoCallFlag &&
17456                     gameMode == IcsPlayingBlack && !blackFlag) {
17457                   SendToICS(ics_prefix);
17458                   SendToICS("flag\n");
17459                 }
17460             } else {
17461                 if (blackFlag) {
17462                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17463                 } else {
17464                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17465                     if (appData.autoCallFlag) {
17466                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17467                         return TRUE;
17468                     }
17469                 }
17470             }
17471         }
17472     }
17473     if (blackTimeRemaining <= 0) {
17474         if (!blackFlag) {
17475             blackFlag = TRUE;
17476             if (appData.icsActive) {
17477                 if (appData.autoCallFlag &&
17478                     gameMode == IcsPlayingWhite && !whiteFlag) {
17479                   SendToICS(ics_prefix);
17480                   SendToICS("flag\n");
17481                 }
17482             } else {
17483                 if (whiteFlag) {
17484                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17485                 } else {
17486                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17487                     if (appData.autoCallFlag) {
17488                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17489                         return TRUE;
17490                     }
17491                 }
17492             }
17493         }
17494     }
17495     return FALSE;
17496 }
17497
17498 void
17499 CheckTimeControl ()
17500 {
17501     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17502         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17503
17504     /*
17505      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17506      */
17507     if ( !WhiteOnMove(forwardMostMove) ) {
17508         /* White made time control */
17509         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17510         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17511         /* [HGM] time odds: correct new time quota for time odds! */
17512                                             / WhitePlayer()->timeOdds;
17513         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17514     } else {
17515         lastBlack -= blackTimeRemaining;
17516         /* Black made time control */
17517         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17518                                             / WhitePlayer()->other->timeOdds;
17519         lastWhite = whiteTimeRemaining;
17520     }
17521 }
17522
17523 void
17524 DisplayBothClocks ()
17525 {
17526     int wom = gameMode == EditPosition ?
17527       !blackPlaysFirst : WhiteOnMove(currentMove);
17528     DisplayWhiteClock(whiteTimeRemaining, wom);
17529     DisplayBlackClock(blackTimeRemaining, !wom);
17530 }
17531
17532
17533 /* Timekeeping seems to be a portability nightmare.  I think everyone
17534    has ftime(), but I'm really not sure, so I'm including some ifdefs
17535    to use other calls if you don't.  Clocks will be less accurate if
17536    you have neither ftime nor gettimeofday.
17537 */
17538
17539 /* VS 2008 requires the #include outside of the function */
17540 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17541 #include <sys/timeb.h>
17542 #endif
17543
17544 /* Get the current time as a TimeMark */
17545 void
17546 GetTimeMark (TimeMark *tm)
17547 {
17548 #if HAVE_GETTIMEOFDAY
17549
17550     struct timeval timeVal;
17551     struct timezone timeZone;
17552
17553     gettimeofday(&timeVal, &timeZone);
17554     tm->sec = (long) timeVal.tv_sec;
17555     tm->ms = (int) (timeVal.tv_usec / 1000L);
17556
17557 #else /*!HAVE_GETTIMEOFDAY*/
17558 #if HAVE_FTIME
17559
17560 // include <sys/timeb.h> / moved to just above start of function
17561     struct timeb timeB;
17562
17563     ftime(&timeB);
17564     tm->sec = (long) timeB.time;
17565     tm->ms = (int) timeB.millitm;
17566
17567 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17568     tm->sec = (long) time(NULL);
17569     tm->ms = 0;
17570 #endif
17571 #endif
17572 }
17573
17574 /* Return the difference in milliseconds between two
17575    time marks.  We assume the difference will fit in a long!
17576 */
17577 long
17578 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17579 {
17580     return 1000L*(tm2->sec - tm1->sec) +
17581            (long) (tm2->ms - tm1->ms);
17582 }
17583
17584
17585 /*
17586  * Code to manage the game clocks.
17587  *
17588  * In tournament play, black starts the clock and then white makes a move.
17589  * We give the human user a slight advantage if he is playing white---the
17590  * clocks don't run until he makes his first move, so it takes zero time.
17591  * Also, we don't account for network lag, so we could get out of sync
17592  * with GNU Chess's clock -- but then, referees are always right.
17593  */
17594
17595 static TimeMark tickStartTM;
17596 static long intendedTickLength;
17597
17598 long
17599 NextTickLength (long timeRemaining)
17600 {
17601     long nominalTickLength, nextTickLength;
17602
17603     if (timeRemaining > 0L && timeRemaining <= 10000L)
17604       nominalTickLength = 100L;
17605     else
17606       nominalTickLength = 1000L;
17607     nextTickLength = timeRemaining % nominalTickLength;
17608     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17609
17610     return nextTickLength;
17611 }
17612
17613 /* Adjust clock one minute up or down */
17614 void
17615 AdjustClock (Boolean which, int dir)
17616 {
17617     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17618     if(which) blackTimeRemaining += 60000*dir;
17619     else      whiteTimeRemaining += 60000*dir;
17620     DisplayBothClocks();
17621     adjustedClock = TRUE;
17622 }
17623
17624 /* Stop clocks and reset to a fresh time control */
17625 void
17626 ResetClocks ()
17627 {
17628     (void) StopClockTimer();
17629     if (appData.icsActive) {
17630         whiteTimeRemaining = blackTimeRemaining = 0;
17631     } else if (searchTime) {
17632         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17633         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17634     } else { /* [HGM] correct new time quote for time odds */
17635         whiteTC = blackTC = fullTimeControlString;
17636         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17637         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17638     }
17639     if (whiteFlag || blackFlag) {
17640         DisplayTitle("");
17641         whiteFlag = blackFlag = FALSE;
17642     }
17643     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17644     DisplayBothClocks();
17645     adjustedClock = FALSE;
17646 }
17647
17648 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17649
17650 /* Decrement running clock by amount of time that has passed */
17651 void
17652 DecrementClocks ()
17653 {
17654     long timeRemaining;
17655     long lastTickLength, fudge;
17656     TimeMark now;
17657
17658     if (!appData.clockMode) return;
17659     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17660
17661     GetTimeMark(&now);
17662
17663     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17664
17665     /* Fudge if we woke up a little too soon */
17666     fudge = intendedTickLength - lastTickLength;
17667     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17668
17669     if (WhiteOnMove(forwardMostMove)) {
17670         if(whiteNPS >= 0) lastTickLength = 0;
17671         timeRemaining = whiteTimeRemaining -= lastTickLength;
17672         if(timeRemaining < 0 && !appData.icsActive) {
17673             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17674             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17675                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17676                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17677             }
17678         }
17679         DisplayWhiteClock(whiteTimeRemaining - fudge,
17680                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17681     } else {
17682         if(blackNPS >= 0) lastTickLength = 0;
17683         timeRemaining = blackTimeRemaining -= lastTickLength;
17684         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17685             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17686             if(suddenDeath) {
17687                 blackStartMove = forwardMostMove;
17688                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17689             }
17690         }
17691         DisplayBlackClock(blackTimeRemaining - fudge,
17692                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17693     }
17694     if (CheckFlags()) return;
17695
17696     if(twoBoards) { // count down secondary board's clocks as well
17697         activePartnerTime -= lastTickLength;
17698         partnerUp = 1;
17699         if(activePartner == 'W')
17700             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17701         else
17702             DisplayBlackClock(activePartnerTime, TRUE);
17703         partnerUp = 0;
17704     }
17705
17706     tickStartTM = now;
17707     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17708     StartClockTimer(intendedTickLength);
17709
17710     /* if the time remaining has fallen below the alarm threshold, sound the
17711      * alarm. if the alarm has sounded and (due to a takeback or time control
17712      * with increment) the time remaining has increased to a level above the
17713      * threshold, reset the alarm so it can sound again.
17714      */
17715
17716     if (appData.icsActive && appData.icsAlarm) {
17717
17718         /* make sure we are dealing with the user's clock */
17719         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17720                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17721            )) return;
17722
17723         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17724             alarmSounded = FALSE;
17725         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17726             PlayAlarmSound();
17727             alarmSounded = TRUE;
17728         }
17729     }
17730 }
17731
17732
17733 /* A player has just moved, so stop the previously running
17734    clock and (if in clock mode) start the other one.
17735    We redisplay both clocks in case we're in ICS mode, because
17736    ICS gives us an update to both clocks after every move.
17737    Note that this routine is called *after* forwardMostMove
17738    is updated, so the last fractional tick must be subtracted
17739    from the color that is *not* on move now.
17740 */
17741 void
17742 SwitchClocks (int newMoveNr)
17743 {
17744     long lastTickLength;
17745     TimeMark now;
17746     int flagged = FALSE;
17747
17748     GetTimeMark(&now);
17749
17750     if (StopClockTimer() && appData.clockMode) {
17751         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17752         if (!WhiteOnMove(forwardMostMove)) {
17753             if(blackNPS >= 0) lastTickLength = 0;
17754             blackTimeRemaining -= lastTickLength;
17755            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17756 //         if(pvInfoList[forwardMostMove].time == -1)
17757                  pvInfoList[forwardMostMove].time =               // use GUI time
17758                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17759         } else {
17760            if(whiteNPS >= 0) lastTickLength = 0;
17761            whiteTimeRemaining -= lastTickLength;
17762            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17763 //         if(pvInfoList[forwardMostMove].time == -1)
17764                  pvInfoList[forwardMostMove].time =
17765                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17766         }
17767         flagged = CheckFlags();
17768     }
17769     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17770     CheckTimeControl();
17771
17772     if (flagged || !appData.clockMode) return;
17773
17774     switch (gameMode) {
17775       case MachinePlaysBlack:
17776       case MachinePlaysWhite:
17777       case BeginningOfGame:
17778         if (pausing) return;
17779         break;
17780
17781       case EditGame:
17782       case PlayFromGameFile:
17783       case IcsExamining:
17784         return;
17785
17786       default:
17787         break;
17788     }
17789
17790     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17791         if(WhiteOnMove(forwardMostMove))
17792              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17793         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17794     }
17795
17796     tickStartTM = now;
17797     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17798       whiteTimeRemaining : blackTimeRemaining);
17799     StartClockTimer(intendedTickLength);
17800 }
17801
17802
17803 /* Stop both clocks */
17804 void
17805 StopClocks ()
17806 {
17807     long lastTickLength;
17808     TimeMark now;
17809
17810     if (!StopClockTimer()) return;
17811     if (!appData.clockMode) return;
17812
17813     GetTimeMark(&now);
17814
17815     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17816     if (WhiteOnMove(forwardMostMove)) {
17817         if(whiteNPS >= 0) lastTickLength = 0;
17818         whiteTimeRemaining -= lastTickLength;
17819         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17820     } else {
17821         if(blackNPS >= 0) lastTickLength = 0;
17822         blackTimeRemaining -= lastTickLength;
17823         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17824     }
17825     CheckFlags();
17826 }
17827
17828 /* Start clock of player on move.  Time may have been reset, so
17829    if clock is already running, stop and restart it. */
17830 void
17831 StartClocks ()
17832 {
17833     (void) StopClockTimer(); /* in case it was running already */
17834     DisplayBothClocks();
17835     if (CheckFlags()) return;
17836
17837     if (!appData.clockMode) return;
17838     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17839
17840     GetTimeMark(&tickStartTM);
17841     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17842       whiteTimeRemaining : blackTimeRemaining);
17843
17844    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17845     whiteNPS = blackNPS = -1;
17846     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17847        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17848         whiteNPS = first.nps;
17849     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17850        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17851         blackNPS = first.nps;
17852     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17853         whiteNPS = second.nps;
17854     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17855         blackNPS = second.nps;
17856     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17857
17858     StartClockTimer(intendedTickLength);
17859 }
17860
17861 char *
17862 TimeString (long ms)
17863 {
17864     long second, minute, hour, day;
17865     char *sign = "";
17866     static char buf[32];
17867
17868     if (ms > 0 && ms <= 9900) {
17869       /* convert milliseconds to tenths, rounding up */
17870       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17871
17872       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17873       return buf;
17874     }
17875
17876     /* convert milliseconds to seconds, rounding up */
17877     /* use floating point to avoid strangeness of integer division
17878        with negative dividends on many machines */
17879     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17880
17881     if (second < 0) {
17882         sign = "-";
17883         second = -second;
17884     }
17885
17886     day = second / (60 * 60 * 24);
17887     second = second % (60 * 60 * 24);
17888     hour = second / (60 * 60);
17889     second = second % (60 * 60);
17890     minute = second / 60;
17891     second = second % 60;
17892
17893     if (day > 0)
17894       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17895               sign, day, hour, minute, second);
17896     else if (hour > 0)
17897       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17898     else
17899       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17900
17901     return buf;
17902 }
17903
17904
17905 /*
17906  * This is necessary because some C libraries aren't ANSI C compliant yet.
17907  */
17908 char *
17909 StrStr (char *string, char *match)
17910 {
17911     int i, length;
17912
17913     length = strlen(match);
17914
17915     for (i = strlen(string) - length; i >= 0; i--, string++)
17916       if (!strncmp(match, string, length))
17917         return string;
17918
17919     return NULL;
17920 }
17921
17922 char *
17923 StrCaseStr (char *string, char *match)
17924 {
17925     int i, j, length;
17926
17927     length = strlen(match);
17928
17929     for (i = strlen(string) - length; i >= 0; i--, string++) {
17930         for (j = 0; j < length; j++) {
17931             if (ToLower(match[j]) != ToLower(string[j]))
17932               break;
17933         }
17934         if (j == length) return string;
17935     }
17936
17937     return NULL;
17938 }
17939
17940 #ifndef _amigados
17941 int
17942 StrCaseCmp (char *s1, char *s2)
17943 {
17944     char c1, c2;
17945
17946     for (;;) {
17947         c1 = ToLower(*s1++);
17948         c2 = ToLower(*s2++);
17949         if (c1 > c2) return 1;
17950         if (c1 < c2) return -1;
17951         if (c1 == NULLCHAR) return 0;
17952     }
17953 }
17954
17955
17956 int
17957 ToLower (int c)
17958 {
17959     return isupper(c) ? tolower(c) : c;
17960 }
17961
17962
17963 int
17964 ToUpper (int c)
17965 {
17966     return islower(c) ? toupper(c) : c;
17967 }
17968 #endif /* !_amigados    */
17969
17970 char *
17971 StrSave (char *s)
17972 {
17973   char *ret;
17974
17975   if ((ret = (char *) malloc(strlen(s) + 1)))
17976     {
17977       safeStrCpy(ret, s, strlen(s)+1);
17978     }
17979   return ret;
17980 }
17981
17982 char *
17983 StrSavePtr (char *s, char **savePtr)
17984 {
17985     if (*savePtr) {
17986         free(*savePtr);
17987     }
17988     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17989       safeStrCpy(*savePtr, s, strlen(s)+1);
17990     }
17991     return(*savePtr);
17992 }
17993
17994 char *
17995 PGNDate ()
17996 {
17997     time_t clock;
17998     struct tm *tm;
17999     char buf[MSG_SIZ];
18000
18001     clock = time((time_t *)NULL);
18002     tm = localtime(&clock);
18003     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18004             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18005     return StrSave(buf);
18006 }
18007
18008
18009 char *
18010 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18011 {
18012     int i, j, fromX, fromY, toX, toY;
18013     int whiteToPlay, haveRights = nrCastlingRights;
18014     char buf[MSG_SIZ];
18015     char *p, *q;
18016     int emptycount;
18017     ChessSquare piece;
18018
18019     whiteToPlay = (gameMode == EditPosition) ?
18020       !blackPlaysFirst : (move % 2 == 0);
18021     p = buf;
18022
18023     /* Piece placement data */
18024     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18025         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18026         emptycount = 0;
18027         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18028             if (boards[move][i][j] == EmptySquare) {
18029                 emptycount++;
18030             } else { ChessSquare piece = boards[move][i][j];
18031                 if (emptycount > 0) {
18032                     if(emptycount<10) /* [HGM] can be >= 10 */
18033                         *p++ = '0' + emptycount;
18034                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18035                     emptycount = 0;
18036                 }
18037                 if(PieceToChar(piece) == '+') {
18038                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18039                     *p++ = '+';
18040                     piece = (ChessSquare)(CHUDEMOTED(piece));
18041                 }
18042                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18043                 if(*p = PieceSuffix(piece)) p++;
18044                 if(p[-1] == '~') {
18045                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18046                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18047                     *p++ = '~';
18048                 }
18049             }
18050         }
18051         if (emptycount > 0) {
18052             if(emptycount<10) /* [HGM] can be >= 10 */
18053                 *p++ = '0' + emptycount;
18054             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18055             emptycount = 0;
18056         }
18057         *p++ = '/';
18058     }
18059     *(p - 1) = ' ';
18060
18061     /* [HGM] print Crazyhouse or Shogi holdings */
18062     if( gameInfo.holdingsWidth ) {
18063         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18064         q = p;
18065         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18066             piece = boards[move][i][BOARD_WIDTH-1];
18067             if( piece != EmptySquare )
18068               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18069                   *p++ = PieceToChar(piece);
18070         }
18071         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18072             piece = boards[move][BOARD_HEIGHT-i-1][0];
18073             if( piece != EmptySquare )
18074               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18075                   *p++ = PieceToChar(piece);
18076         }
18077
18078         if( q == p ) *p++ = '-';
18079         *p++ = ']';
18080         *p++ = ' ';
18081     }
18082
18083     /* Active color */
18084     *p++ = whiteToPlay ? 'w' : 'b';
18085     *p++ = ' ';
18086
18087   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18088     haveRights = 0; q = p;
18089     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18090       piece = boards[move][0][i];
18091       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18092         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18093       }
18094     }
18095     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18096       piece = boards[move][BOARD_HEIGHT-1][i];
18097       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18098         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18099       }
18100     }
18101     if(p == q) *p++ = '-';
18102     *p++ = ' ';
18103   }
18104
18105   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18106     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18107   } else {
18108   if(haveRights) {
18109      int handW=0, handB=0;
18110      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18111         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18112         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18113      }
18114      q = p;
18115      if(appData.fischerCastling) {
18116         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18117            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18118                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18119         } else {
18120        /* [HGM] write directly from rights */
18121            if(boards[move][CASTLING][2] != NoRights &&
18122               boards[move][CASTLING][0] != NoRights   )
18123                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18124            if(boards[move][CASTLING][2] != NoRights &&
18125               boards[move][CASTLING][1] != NoRights   )
18126                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18127         }
18128         if(handB) {
18129            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18130                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18131         } else {
18132            if(boards[move][CASTLING][5] != NoRights &&
18133               boards[move][CASTLING][3] != NoRights   )
18134                 *p++ = boards[move][CASTLING][3] + AAA;
18135            if(boards[move][CASTLING][5] != NoRights &&
18136               boards[move][CASTLING][4] != NoRights   )
18137                 *p++ = boards[move][CASTLING][4] + AAA;
18138         }
18139      } else {
18140
18141         /* [HGM] write true castling rights */
18142         if( nrCastlingRights == 6 ) {
18143             int q, k=0;
18144             if(boards[move][CASTLING][0] != NoRights &&
18145                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18146             q = (boards[move][CASTLING][1] != NoRights &&
18147                  boards[move][CASTLING][2] != NoRights  );
18148             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18149                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18150                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18151                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18152             }
18153             if(q) *p++ = 'Q';
18154             k = 0;
18155             if(boards[move][CASTLING][3] != NoRights &&
18156                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18157             q = (boards[move][CASTLING][4] != NoRights &&
18158                  boards[move][CASTLING][5] != NoRights  );
18159             if(handB) {
18160                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18161                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18162                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18163             }
18164             if(q) *p++ = 'q';
18165         }
18166      }
18167      if (q == p) *p++ = '-'; /* No castling rights */
18168      *p++ = ' ';
18169   }
18170
18171   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18172      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18173      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18174     /* En passant target square */
18175     if (move > backwardMostMove) {
18176         fromX = moveList[move - 1][0] - AAA;
18177         fromY = moveList[move - 1][1] - ONE;
18178         toX = moveList[move - 1][2] - AAA;
18179         toY = moveList[move - 1][3] - ONE;
18180         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18181             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18182             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18183             fromX == toX) {
18184             /* 2-square pawn move just happened */
18185             *p++ = toX + AAA;
18186             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18187         } else {
18188             *p++ = '-';
18189         }
18190     } else if(move == backwardMostMove) {
18191         // [HGM] perhaps we should always do it like this, and forget the above?
18192         if((signed char)boards[move][EP_STATUS] >= 0) {
18193             *p++ = boards[move][EP_STATUS] + AAA;
18194             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18195         } else {
18196             *p++ = '-';
18197         }
18198     } else {
18199         *p++ = '-';
18200     }
18201     *p++ = ' ';
18202   }
18203   }
18204
18205     if(moveCounts)
18206     {   int i = 0, j=move;
18207
18208         /* [HGM] find reversible plies */
18209         if (appData.debugMode) { int k;
18210             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18211             for(k=backwardMostMove; k<=forwardMostMove; k++)
18212                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18213
18214         }
18215
18216         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18217         if( j == backwardMostMove ) i += initialRulePlies;
18218         sprintf(p, "%d ", i);
18219         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18220
18221         /* Fullmove number */
18222         sprintf(p, "%d", (move / 2) + 1);
18223     } else *--p = NULLCHAR;
18224
18225     return StrSave(buf);
18226 }
18227
18228 Boolean
18229 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18230 {
18231     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18232     char *p, c;
18233     int emptycount, virgin[BOARD_FILES];
18234     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18235
18236     p = fen;
18237
18238     /* Piece placement data */
18239     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18240         j = 0;
18241         for (;;) {
18242             if (*p == '/' || *p == ' ' || *p == '[' ) {
18243                 if(j > w) w = j;
18244                 emptycount = gameInfo.boardWidth - j;
18245                 while (emptycount--)
18246                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18247                 if (*p == '/') p++;
18248                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18249                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18250                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18251                     }
18252                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18253                 }
18254                 break;
18255 #if(BOARD_FILES >= 10)*0
18256             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18257                 p++; emptycount=10;
18258                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18259                 while (emptycount--)
18260                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18261 #endif
18262             } else if (*p == '*') {
18263                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18264             } else if (isdigit(*p)) {
18265                 emptycount = *p++ - '0';
18266                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18267                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18268                 while (emptycount--)
18269                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18270             } else if (*p == '<') {
18271                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18272                 else if (i != 0 || !shuffle) return FALSE;
18273                 p++;
18274             } else if (shuffle && *p == '>') {
18275                 p++; // for now ignore closing shuffle range, and assume rank-end
18276             } else if (*p == '?') {
18277                 if (j >= gameInfo.boardWidth) return FALSE;
18278                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18279                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18280             } else if (*p == '+' || isalpha(*p)) {
18281                 char *q, *s = SUFFIXES;
18282                 if (j >= gameInfo.boardWidth) return FALSE;
18283                 if(*p=='+') {
18284                     char c = *++p;
18285                     if(q = strchr(s, p[1])) p++;
18286                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18287                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18288                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18289                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18290                 } else {
18291                     char c = *p++;
18292                     if(q = strchr(s, *p)) p++;
18293                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18294                 }
18295
18296                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18297                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18298                     piece = (ChessSquare) (PROMOTED(piece));
18299                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18300                     p++;
18301                 }
18302                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18303                 if(piece == king) wKingRank = i;
18304                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18305             } else {
18306                 return FALSE;
18307             }
18308         }
18309     }
18310     while (*p == '/' || *p == ' ') p++;
18311
18312     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18313
18314     /* [HGM] by default clear Crazyhouse holdings, if present */
18315     if(gameInfo.holdingsWidth) {
18316        for(i=0; i<BOARD_HEIGHT; i++) {
18317            board[i][0]             = EmptySquare; /* black holdings */
18318            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18319            board[i][1]             = (ChessSquare) 0; /* black counts */
18320            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18321        }
18322     }
18323
18324     /* [HGM] look for Crazyhouse holdings here */
18325     while(*p==' ') p++;
18326     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18327         int swap=0, wcnt=0, bcnt=0;
18328         if(*p == '[') p++;
18329         if(*p == '<') swap++, p++;
18330         if(*p == '-' ) p++; /* empty holdings */ else {
18331             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18332             /* if we would allow FEN reading to set board size, we would   */
18333             /* have to add holdings and shift the board read so far here   */
18334             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18335                 p++;
18336                 if((int) piece >= (int) BlackPawn ) {
18337                     i = (int)piece - (int)BlackPawn;
18338                     i = PieceToNumber((ChessSquare)i);
18339                     if( i >= gameInfo.holdingsSize ) return FALSE;
18340                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18341                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18342                     bcnt++;
18343                 } else {
18344                     i = (int)piece - (int)WhitePawn;
18345                     i = PieceToNumber((ChessSquare)i);
18346                     if( i >= gameInfo.holdingsSize ) return FALSE;
18347                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18348                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18349                     wcnt++;
18350                 }
18351             }
18352             if(subst) { // substitute back-rank question marks by holdings pieces
18353                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18354                     int k, m, n = bcnt + 1;
18355                     if(board[0][j] == ClearBoard) {
18356                         if(!wcnt) return FALSE;
18357                         n = rand() % wcnt;
18358                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18359                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18360                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18361                             break;
18362                         }
18363                     }
18364                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18365                         if(!bcnt) return FALSE;
18366                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18367                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18368                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18369                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18370                             break;
18371                         }
18372                     }
18373                 }
18374                 subst = 0;
18375             }
18376         }
18377         if(*p == ']') p++;
18378     }
18379
18380     if(subst) return FALSE; // substitution requested, but no holdings
18381
18382     while(*p == ' ') p++;
18383
18384     /* Active color */
18385     c = *p++;
18386     if(appData.colorNickNames) {
18387       if( c == appData.colorNickNames[0] ) c = 'w'; else
18388       if( c == appData.colorNickNames[1] ) c = 'b';
18389     }
18390     switch (c) {
18391       case 'w':
18392         *blackPlaysFirst = FALSE;
18393         break;
18394       case 'b':
18395         *blackPlaysFirst = TRUE;
18396         break;
18397       default:
18398         return FALSE;
18399     }
18400
18401     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18402     /* return the extra info in global variiables             */
18403
18404     while(*p==' ') p++;
18405
18406     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18407         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18408         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18409     }
18410
18411     /* set defaults in case FEN is incomplete */
18412     board[EP_STATUS] = EP_UNKNOWN;
18413     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18414     for(i=0; i<nrCastlingRights; i++ ) {
18415         board[CASTLING][i] =
18416             appData.fischerCastling ? NoRights : initialRights[i];
18417     }   /* assume possible unless obviously impossible */
18418     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18419     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18420     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18421                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18422     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18423     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18424     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18425                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18426     FENrulePlies = 0;
18427
18428     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18429       char *q = p;
18430       int w=0, b=0;
18431       while(isalpha(*p)) {
18432         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18433         if(islower(*p)) b |= 1 << (*p++ - 'a');
18434       }
18435       if(*p == '-') p++;
18436       if(p != q) {
18437         board[TOUCHED_W] = ~w;
18438         board[TOUCHED_B] = ~b;
18439         while(*p == ' ') p++;
18440       }
18441     } else
18442
18443     if(nrCastlingRights) {
18444       int fischer = 0;
18445       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18446       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18447           /* castling indicator present, so default becomes no castlings */
18448           for(i=0; i<nrCastlingRights; i++ ) {
18449                  board[CASTLING][i] = NoRights;
18450           }
18451       }
18452       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18453              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18454              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18455              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18456         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18457
18458         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18459             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18460             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18461         }
18462         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18463             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18464         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18465                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18466         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18467                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18468         switch(c) {
18469           case'K':
18470               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18471               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18472               board[CASTLING][2] = whiteKingFile;
18473               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18474               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18475               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18476               break;
18477           case'Q':
18478               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18479               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18480               board[CASTLING][2] = whiteKingFile;
18481               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18482               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18483               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18484               break;
18485           case'k':
18486               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18487               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18488               board[CASTLING][5] = blackKingFile;
18489               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18490               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18491               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18492               break;
18493           case'q':
18494               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18495               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18496               board[CASTLING][5] = blackKingFile;
18497               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18498               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18499               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18500           case '-':
18501               break;
18502           default: /* FRC castlings */
18503               if(c >= 'a') { /* black rights */
18504                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18505                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18506                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18507                   if(i == BOARD_RGHT) break;
18508                   board[CASTLING][5] = i;
18509                   c -= AAA;
18510                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18511                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18512                   if(c > i)
18513                       board[CASTLING][3] = c;
18514                   else
18515                       board[CASTLING][4] = c;
18516               } else { /* white rights */
18517                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18518                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18519                     if(board[0][i] == WhiteKing) break;
18520                   if(i == BOARD_RGHT) break;
18521                   board[CASTLING][2] = i;
18522                   c -= AAA - 'a' + 'A';
18523                   if(board[0][c] >= WhiteKing) break;
18524                   if(c > i)
18525                       board[CASTLING][0] = c;
18526                   else
18527                       board[CASTLING][1] = c;
18528               }
18529         }
18530       }
18531       for(i=0; i<nrCastlingRights; i++)
18532         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18533       if(gameInfo.variant == VariantSChess)
18534         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18535       if(fischer && shuffle) appData.fischerCastling = TRUE;
18536     if (appData.debugMode) {
18537         fprintf(debugFP, "FEN castling rights:");
18538         for(i=0; i<nrCastlingRights; i++)
18539         fprintf(debugFP, " %d", board[CASTLING][i]);
18540         fprintf(debugFP, "\n");
18541     }
18542
18543       while(*p==' ') p++;
18544     }
18545
18546     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18547
18548     /* read e.p. field in games that know e.p. capture */
18549     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18550        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18551        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18552       if(*p=='-') {
18553         p++; board[EP_STATUS] = EP_NONE;
18554       } else {
18555          char c = *p++ - AAA;
18556
18557          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18558          if(*p >= '0' && *p <='9') p++;
18559          board[EP_STATUS] = c;
18560       }
18561     }
18562
18563
18564     if(sscanf(p, "%d", &i) == 1) {
18565         FENrulePlies = i; /* 50-move ply counter */
18566         /* (The move number is still ignored)    */
18567     }
18568
18569     return TRUE;
18570 }
18571
18572 void
18573 EditPositionPasteFEN (char *fen)
18574 {
18575   if (fen != NULL) {
18576     Board initial_position;
18577
18578     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18579       DisplayError(_("Bad FEN position in clipboard"), 0);
18580       return ;
18581     } else {
18582       int savedBlackPlaysFirst = blackPlaysFirst;
18583       EditPositionEvent();
18584       blackPlaysFirst = savedBlackPlaysFirst;
18585       CopyBoard(boards[0], initial_position);
18586       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18587       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18588       DisplayBothClocks();
18589       DrawPosition(FALSE, boards[currentMove]);
18590     }
18591   }
18592 }
18593
18594 static char cseq[12] = "\\   ";
18595
18596 Boolean
18597 set_cont_sequence (char *new_seq)
18598 {
18599     int len;
18600     Boolean ret;
18601
18602     // handle bad attempts to set the sequence
18603         if (!new_seq)
18604                 return 0; // acceptable error - no debug
18605
18606     len = strlen(new_seq);
18607     ret = (len > 0) && (len < sizeof(cseq));
18608     if (ret)
18609       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18610     else if (appData.debugMode)
18611       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18612     return ret;
18613 }
18614
18615 /*
18616     reformat a source message so words don't cross the width boundary.  internal
18617     newlines are not removed.  returns the wrapped size (no null character unless
18618     included in source message).  If dest is NULL, only calculate the size required
18619     for the dest buffer.  lp argument indicats line position upon entry, and it's
18620     passed back upon exit.
18621 */
18622 int
18623 wrap (char *dest, char *src, int count, int width, int *lp)
18624 {
18625     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18626
18627     cseq_len = strlen(cseq);
18628     old_line = line = *lp;
18629     ansi = len = clen = 0;
18630
18631     for (i=0; i < count; i++)
18632     {
18633         if (src[i] == '\033')
18634             ansi = 1;
18635
18636         // if we hit the width, back up
18637         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18638         {
18639             // store i & len in case the word is too long
18640             old_i = i, old_len = len;
18641
18642             // find the end of the last word
18643             while (i && src[i] != ' ' && src[i] != '\n')
18644             {
18645                 i--;
18646                 len--;
18647             }
18648
18649             // word too long?  restore i & len before splitting it
18650             if ((old_i-i+clen) >= width)
18651             {
18652                 i = old_i;
18653                 len = old_len;
18654             }
18655
18656             // extra space?
18657             if (i && src[i-1] == ' ')
18658                 len--;
18659
18660             if (src[i] != ' ' && src[i] != '\n')
18661             {
18662                 i--;
18663                 if (len)
18664                     len--;
18665             }
18666
18667             // now append the newline and continuation sequence
18668             if (dest)
18669                 dest[len] = '\n';
18670             len++;
18671             if (dest)
18672                 strncpy(dest+len, cseq, cseq_len);
18673             len += cseq_len;
18674             line = cseq_len;
18675             clen = cseq_len;
18676             continue;
18677         }
18678
18679         if (dest)
18680             dest[len] = src[i];
18681         len++;
18682         if (!ansi)
18683             line++;
18684         if (src[i] == '\n')
18685             line = 0;
18686         if (src[i] == 'm')
18687             ansi = 0;
18688     }
18689     if (dest && appData.debugMode)
18690     {
18691         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18692             count, width, line, len, *lp);
18693         show_bytes(debugFP, src, count);
18694         fprintf(debugFP, "\ndest: ");
18695         show_bytes(debugFP, dest, len);
18696         fprintf(debugFP, "\n");
18697     }
18698     *lp = dest ? line : old_line;
18699
18700     return len;
18701 }
18702
18703 // [HGM] vari: routines for shelving variations
18704 Boolean modeRestore = FALSE;
18705
18706 void
18707 PushInner (int firstMove, int lastMove)
18708 {
18709         int i, j, nrMoves = lastMove - firstMove;
18710
18711         // push current tail of game on stack
18712         savedResult[storedGames] = gameInfo.result;
18713         savedDetails[storedGames] = gameInfo.resultDetails;
18714         gameInfo.resultDetails = NULL;
18715         savedFirst[storedGames] = firstMove;
18716         savedLast [storedGames] = lastMove;
18717         savedFramePtr[storedGames] = framePtr;
18718         framePtr -= nrMoves; // reserve space for the boards
18719         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18720             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18721             for(j=0; j<MOVE_LEN; j++)
18722                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18723             for(j=0; j<2*MOVE_LEN; j++)
18724                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18725             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18726             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18727             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18728             pvInfoList[firstMove+i-1].depth = 0;
18729             commentList[framePtr+i] = commentList[firstMove+i];
18730             commentList[firstMove+i] = NULL;
18731         }
18732
18733         storedGames++;
18734         forwardMostMove = firstMove; // truncate game so we can start variation
18735 }
18736
18737 void
18738 PushTail (int firstMove, int lastMove)
18739 {
18740         if(appData.icsActive) { // only in local mode
18741                 forwardMostMove = currentMove; // mimic old ICS behavior
18742                 return;
18743         }
18744         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18745
18746         PushInner(firstMove, lastMove);
18747         if(storedGames == 1) GreyRevert(FALSE);
18748         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18749 }
18750
18751 void
18752 PopInner (Boolean annotate)
18753 {
18754         int i, j, nrMoves;
18755         char buf[8000], moveBuf[20];
18756
18757         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18758         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18759         nrMoves = savedLast[storedGames] - currentMove;
18760         if(annotate) {
18761                 int cnt = 10;
18762                 if(!WhiteOnMove(currentMove))
18763                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18764                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18765                 for(i=currentMove; i<forwardMostMove; i++) {
18766                         if(WhiteOnMove(i))
18767                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18768                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18769                         strcat(buf, moveBuf);
18770                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18771                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18772                 }
18773                 strcat(buf, ")");
18774         }
18775         for(i=1; i<=nrMoves; i++) { // copy last variation back
18776             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18777             for(j=0; j<MOVE_LEN; j++)
18778                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18779             for(j=0; j<2*MOVE_LEN; j++)
18780                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18781             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18782             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18783             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18784             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18785             commentList[currentMove+i] = commentList[framePtr+i];
18786             commentList[framePtr+i] = NULL;
18787         }
18788         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18789         framePtr = savedFramePtr[storedGames];
18790         gameInfo.result = savedResult[storedGames];
18791         if(gameInfo.resultDetails != NULL) {
18792             free(gameInfo.resultDetails);
18793       }
18794         gameInfo.resultDetails = savedDetails[storedGames];
18795         forwardMostMove = currentMove + nrMoves;
18796 }
18797
18798 Boolean
18799 PopTail (Boolean annotate)
18800 {
18801         if(appData.icsActive) return FALSE; // only in local mode
18802         if(!storedGames) return FALSE; // sanity
18803         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18804
18805         PopInner(annotate);
18806         if(currentMove < forwardMostMove) ForwardEvent(); else
18807         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18808
18809         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18810         return TRUE;
18811 }
18812
18813 void
18814 CleanupTail ()
18815 {       // remove all shelved variations
18816         int i;
18817         for(i=0; i<storedGames; i++) {
18818             if(savedDetails[i])
18819                 free(savedDetails[i]);
18820             savedDetails[i] = NULL;
18821         }
18822         for(i=framePtr; i<MAX_MOVES; i++) {
18823                 if(commentList[i]) free(commentList[i]);
18824                 commentList[i] = NULL;
18825         }
18826         framePtr = MAX_MOVES-1;
18827         storedGames = 0;
18828 }
18829
18830 void
18831 LoadVariation (int index, char *text)
18832 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18833         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18834         int level = 0, move;
18835
18836         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18837         // first find outermost bracketing variation
18838         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18839             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18840                 if(*p == '{') wait = '}'; else
18841                 if(*p == '[') wait = ']'; else
18842                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18843                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18844             }
18845             if(*p == wait) wait = NULLCHAR; // closing ]} found
18846             p++;
18847         }
18848         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18849         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18850         end[1] = NULLCHAR; // clip off comment beyond variation
18851         ToNrEvent(currentMove-1);
18852         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18853         // kludge: use ParsePV() to append variation to game
18854         move = currentMove;
18855         ParsePV(start, TRUE, TRUE);
18856         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18857         ClearPremoveHighlights();
18858         CommentPopDown();
18859         ToNrEvent(currentMove+1);
18860 }
18861
18862 void
18863 LoadTheme ()
18864 {
18865     char *p, *q, buf[MSG_SIZ];
18866     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18867         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18868         ParseArgsFromString(buf);
18869         ActivateTheme(TRUE); // also redo colors
18870         return;
18871     }
18872     p = nickName;
18873     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18874     {
18875         int len;
18876         q = appData.themeNames;
18877         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18878       if(appData.useBitmaps) {
18879         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18880                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18881                 appData.liteBackTextureMode,
18882                 appData.darkBackTextureMode );
18883       } else {
18884         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18885                 Col2Text(2),   // lightSquareColor
18886                 Col2Text(3) ); // darkSquareColor
18887       }
18888       if(appData.useBorder) {
18889         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18890                 appData.border);
18891       } else {
18892         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18893       }
18894       if(appData.useFont) {
18895         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18896                 appData.renderPiecesWithFont,
18897                 appData.fontToPieceTable,
18898                 Col2Text(9),    // appData.fontBackColorWhite
18899                 Col2Text(10) ); // appData.fontForeColorBlack
18900       } else {
18901         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18902                 appData.pieceDirectory);
18903         if(!appData.pieceDirectory[0])
18904           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18905                 Col2Text(0),   // whitePieceColor
18906                 Col2Text(1) ); // blackPieceColor
18907       }
18908       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18909                 Col2Text(4),   // highlightSquareColor
18910                 Col2Text(5) ); // premoveHighlightColor
18911         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18912         if(insert != q) insert[-1] = NULLCHAR;
18913         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18914         if(q)   free(q);
18915     }
18916     ActivateTheme(FALSE);
18917 }